Skip to content

Products API

Ürün ekleme, güncelleme, listeleme, resim yükleme, marka & kategori auto-resolve.

Base URL: https://api.entegrasyoner.com/v1

Auth: Tüm endpoint'lerde Authorization: Bearer <API_KEY> zorunlu — bkz. Authentication.

İçerik

CRUD & listeleme

Görsel yönetimi

  • POST /products/{id}/images — resim ekle (multipart)
  • [PUT /products/{id}/images/reorder] — resim sırasını güncelle
  • [DELETE /products/{id}/images/{imageId}] — resim sil

Audit & debug

Payload şeması — POST ve PUT

Tüm alanlar opsiyoneldir (PUT'ta partial update — gönderilmeyen alanlar değişmez). Yalnızca POST için name zorunludur.

Kimlik & Tanımlayıcı

AlanTipAçıklama
namestring (POST'ta zorunlu)Ürün adı
modelstringÜrün kodu — lookup'ta stabil identifier
skustringStok kodu
eanstringBarkod
descriptionstringHTML açıklama
statusbooltrue = aktif (pazaryerlerinde satışta)
publish_enabledboolfalse = bu ürünü hiçbir pazaryerine gönderme (toplu fren)

Fiyat & Vergi

Sistem KDV-hariç fiyatı (price) ve tax_rate_id'yi DB'de saklar. KDV-dahil değeri runtime'da hesaplanır. Aşağıdaki kombinasyonlardan herhangi birini gönderebilirsin — sistem doğru çevirimi yapar:

AlanTipAçıklama
pricenumericKDV-hariç fiyat
price_with_vatnumericKDV-dahil fiyat — verilirse price otomatik hesaplanır (reverse-compute)
tax_ratenumericKDV oranı yüzdesi (örn. 20) — sistemdeki TaxRate'e göre resolve edilir
tax_rate_idintTaxRate FK ID (yüzde verilirse yüzde öncelikli)

Öncelik mantığı:

  1. tax_rate (yüzde) → varsa kullan, yoksa tax_rate_id
  2. price_with_vat > 0 → KDV-hariç price = price_with_vat / (1 + tax_rate/100)
  3. Else price > 0 → doğrudan kullan
  4. İkisi de 0/boş → fiyat değişmez (kazara sıfırlama yok)

Geçersiz tax_rate: Sistemde tanımlı oran yoksa 422 döner:

json
{
  "message": "Geçersiz KDV oranı: 18. Tanımlı oranlar: 0, 1, 10, 20",
  "errors": {"tax_rate": ["Geçersiz KDV oranı: 18. Tanımlı oranlar: 0, 1, 10, 20"]}
}

Pazaryeri-özel fiyat — channel_overrides

price/price_with_vat master (genel) fiyattır — tüm kanalların başlangıç noktası. Bir ürünü belirli bir pazaryerine farklı fiyat/stokla göndermek istersen channel_overrides ekle. Hedef pazaryerini credential_id ile belirtirsin — bu id'leri GET /v1/channels ile öğrenirsin.

json
{
  "name": "Örnek Ürün",
  "price": 100,
  "channel_overrides": [
    { "credential_id": 5, "price": 130 },
    { "credential_id": 7, "markup_percent": 35 },
    { "credential_id": 5, "stock": 8 }
  ]
}
AlanAçıklama
credential_idZorunlu. Hedef pazaryeri bağlantısı — GET /v1/channels'tan alınır. Override o bağlantının pazaryeri seviyesinde uygulanır.
priceO pazaryerine giden mutlak fiyat (KDV-hariç). Markup/KDV bypass edilir.
markup_percentMutlak yerine % override (master fiyat üzerine). price ile birlikte gönderilemez (422).
stockO pazaryerine özel stok (opsiyonel).
  • Çözüm önceliği: price (mutlak) → markup_percent (%) → bağlantı/kategori markup'ı → bağlantı varsayılanı.
  • null göndermek o override'ı temizler; alanı hiç göndermezsen mevcut override korunur.
  • Eşleme henüz yoksa: override pending saklanır; ürün o pazaryerine ilk yayınlandığında (manuel eşleme / barkod auto-resolve) otomatik uygulanır — tekrar göndermene gerek yok.
  • Aynı farklılaştırma portalde de yapılabilir (ürün formu → "Pazaryeri Fiyatları"). Tüm kanallara eşit % istiyorsan bağlantı markup'ı (portal) daha pratiktir — bkz. Pazaryerleri → fiyat markup.

Marka & Kategori

İki yol vardır. İkisini de gönderirsen *_name önceliklidir (yanlışlıkla eski ID gönderildiğinde isim override eder).

Önerilen yaklaşım: Marka ve kategorileri önce sistemde oluştur (POST /brands, POST /categories), bir kez ID'lerini al, ürün gönderirken bu ID'leri kullan. Tipo ile kazara yeni marka oluşturma riski yok, daha hızlı (her ürün için lookup yapılmaz) ve audit/raporlama temiz kalır. Bizim sistemde bir kere oluşturulan marka/kategori tüm pazaryerlerine senkron edilir.

Önerilen — ID ile

json
{
  "brand_id": 12,
  "category_ids": [45, 67]
}

Önce GET /brands ve GET /categories ile listeyi çek (örn. başlangıçta bir kez veya cache'le), sonra ID gönder.

Hızlı — isim ile (find-or-create)

json
{
  "brand_name": "ExampleBrand",
  "category_paths": [["Electronics", "Smartphones"]],
  "category_names": ["New Arrivals"]
}
  • brand_name — case-insensitive eşleşir ("ExampleBrand" ve "EXAMPLEBRAND" aynı brand). Yoksa yaratılır.
  • category_paths — hiyerarşik liste: her path için root'tan leaf'e doğru her seviye firstOrCreate. Leaf bağlanır.
  • category_names — flat root liste (parent=null) için kısayol.

Duplicate oluşmaz: aynı isimde marka veya aynı path tekrar gönderilirse mevcut id reuse edilir.

Stok

AlanTipAçıklama
quantityintÜrünün genel stoğu (varsayılan lokasyon). Çoğu entegrasyon yalnızca bunu gönderir.
attribute_bagobject<string,int>Pazaryeri mağaza-bazlı stok: mağaza-kodu → adet. Örn. {"TR-IST-01": 20, "TR-ANK-01": 15}. Sadece belirli bir pazaryeri mağazasına özel stok ayırıyorsan kullan — genel stoğu buraya yazma, o quantity'dir. Key'ler GET /v1/stock-locations'taki code değerleridir.

Tek depodan satıyorsan attribute_bag'e hiç dokunma — sadece quantity yeterli. attribute_bag yalnızca TrendyolGo gibi çok-mağazalı pazaryerlerinde her şubeye farklı stok vermek istediğinde gerekir.

Mağaza lokasyonlarını sorgula

http
GET /v1/stock-locations
Authorization: Bearer <API_KEY>

Response — 200 OK:

json
{
  "data": [
    {"id": 1, "code": "DEFAULT", "label": "Genel Stok", "type": "local", "is_default": true, "is_active": true},
    {"id": 2, "code": "TR-IST-01", "label": "TrendyolGo İstanbul Şube", "type": "marketplace_store", "is_default": false, "is_active": true},
    {"id": 3, "code": "TR-ANK-01", "label": "TrendyolGo Ankara Şube", "type": "marketplace_store", "is_default": false, "is_active": true}
  ]
}

Liste filtreleri: type (local/marketplace_store/external), active_only (default true).

Tip açıklaması:

  • local — varsayılan lokasyon; quantity buraya yazar. Her tenant'ta tek tanedir.
  • marketplace_store — pazaryeri bağlantısıyla otomatik oluşur (örn. TrendyolGo'nun her şube mağazası). attribute_bag key'leri tipik olarak bunlardır.
  • external — manuel eklediğin ek lokasyon (POST /v1/stock-locations ile type=external).

Manuel lokasyon ekle (yalnız type=external — local/marketplace_store sistem-yönetilir):

http
POST /v1/stock-locations
Authorization: Bearer <API_KEY>
Content-Type: application/json

{"code": "DEPO-2", "label": "İkinci Depo", "type": "external"}

Yanıt 201: {message, data}. Aynı code ikinci kez yazılamaz (422).

Güncelle (yalnız label ve is_active — marketplace_store için de geçerli):

http
PATCH /v1/stock-locations/5
Authorization: Bearer <API_KEY>
Content-Type: application/json

{"label": "İkinci Depo (pasif)", "is_active": false}

Sil (yalnız type=external; marketplace_store credential üzerinden yönetilir):

http
DELETE /v1/stock-locations/5
Authorization: Bearer <API_KEY>

KDV oranları — GET /v1/tax-rates

Tanımlı KDV oranlarını döner; ürün eklerken tax_rate (yüzde) yerine tax_rate_id kullanmak istersen buradan id alırsın.

http
GET /v1/tax-rates
Authorization: Bearer <API_KEY>

Response — 200 OK:

json
{"data": [{"id": 1, "name": "KDV (%10)", "rate": 10}, {"id": 2, "name": "KDV (%20)", "rate": 20}]}

Örnekler

1) Yeni ürün — marka + hierarchical kategori

Request: (en yaygın senaryo — KDV-hariç fiyat + tek genel stok)

http
POST /v1/products
Authorization: Bearer <API_KEY>
Content-Type: application/json

{
  "name": "Example Product 250ml",
  "model": "EX-250",
  "sku": "EX-SKU-001",
  "ean": "1234567890123",
  "description": "Generic example product",
  "price": 300,
  "tax_rate": 20,
  "quantity": 50,
  "status": true,
  "publish_enabled": true,
  "brand_name": "ExampleBrand",
  "category_paths": [["Electronics", "Smartphones"]]
}

Response — 201 Created:

json
{
  "message": "Ürün oluşturuldu.",
  "id": 1001,
  "brand_id": 12,
  "category_ids": [45]
}

DB'ye yazılan değerler:

  • price = 300 (KDV-hariç) · tax_rate_id = 2 (%20) · price_with_vat = 360 (hesaplanır)
  • brand_id = 12 (ExampleBrand — var olan match veya yeni create)
  • categories = [45] (Smartphones leaf; parent Electronics tree'de auto-create)
  • quantity = 50 (genel/varsayılan stok)

KDV-dahil göndermek istersen: price yerine "price_with_vat": 360 yolla — sistem KDV-hariç'i (300) geri hesaplar. İkisini birden gönderme; biri yeter. Mağaza-bazlı stok istersen: "attribute_bag": {"TR-IST-01": 20, "TR-ANK-01": 15} ekle (bkz. Stok).

2) Resim ekle (multipart)

http
POST /v1/products/1001/images
Authorization: Bearer <API_KEY>
Accept: application/json
Content-Type: multipart/form-data

image=@/path/to/photo.jpg

Kabul edilen format: jpeg|jpg|png|webp, maks 5MB.

Response — 201 Created:

json
{
  "id": 5001,
  "url": "<image-url>"
}

Aynı endpoint'i N kez çağırarak birden fazla resim ekleyebilirsin. Sıra sort_order ile DB'de tutulur, ilk eklenen birincil görsel.

Sırala — görsel sırasını topluca güncelle (ilk eleman birincil görsel olur):

http
PUT /v1/products/1001/images/reorder
Authorization: Bearer <API_KEY>
Content-Type: application/json

{"image_ids": [5003, 5001, 5002]}

Yalnızca bu ürüne ait image_id'ler dikkate alınır; yabancı id'ler sessizce atlanır. Yanıt 200: {"message": "Görsel sırası güncellendi."}.

Sil — tek görsel kaldır:

http
DELETE /v1/products/1001/images/5001
Authorization: Bearer <API_KEY>

Yanıt 200: {"message": "Görsel silindi."}. Dosya storage'dan da silinir.

3) Güncelle — sadece fiyat ve stok

http
PUT /v1/products/1001
Authorization: Bearer <API_KEY>
Content-Type: application/json

{
  "name": "Example Product 250ml",
  "price_with_vat": 420,
  "tax_rate": 20,
  "attribute_bag": {"TR-IST-01": 12}
}

Diğer alanlar (sku, ean, status, kategoriler) değişmez. attribute_bag merge semantiği kullanır — sadece verilen key'ler güncellenir.

⚠️ name PUT'ta da gönderilmesi gerekir (model'in required validation'ı). İleride opsiyonel yapılabilir.

4) Listele — paginate + filter

http
GET /v1/products?brand_id=12&status=true&has_stock=true&per_page=50&page=1
Authorization: Bearer <API_KEY>
Accept: application/json

Query parametreleri:

  • search — name/sku/ean/model'da fragmented LIKE
  • brand_id, category_id, group_id — filtre
  • statustrue/false
  • has_stock, has_imagetrue/false
  • mapped_platform_id, unmapped_platform_id — pazaryeri eşleme filtresi
  • has_channel_overridetrue/false: kanala özel fiyat/stok override'ı olan ürünler
  • override_source — override kaynağı (manual | api | inbound)
  • per_page (default 20), page

Response — 200 OK:

json
{
  "data": [
    {
      "id": 1001,
      "name": "Example Product 250ml",
      "sku": "EX-SKU-001",
      "price": 300,
      "price_with_vat": 360,
      "quantity": 50,
      "status": true,
      "brand": {"id": 12, "name": "ExampleBrand"},
      "tax_rate": {"id": 2, "name": "KDV (%20)", "rate": 20},
      "image": "<image-url>",
      "attribute_bag": {"TR-IST-01": 20, "TR-ANK-01": 15, "TR-IZM-01": 15}
    }
  ],
  "links": {},
  "meta": {"current_page": 1, "per_page": 50, "total": 1}
}

5) Tek ürün

http
GET /v1/products/1001
Authorization: Bearer <API_KEY>

İçinde mappings, categories, groups, descriptions yüklü gelir.

6) Sil

http
DELETE /v1/products/1001
Authorization: Bearer <API_KEY>

Response — 200 OK:

json
{"message": "Ürün silindi."}

Ürün soft-delete değil — pazaryeri eşlemeleri de cascade silinir. Geri alma yok.

Yayın akışı (otomatik)

Pazaryerine yayın bizim tarafımızda otomatik çalışır — sen ürünü ekledikten veya güncelledikten sonra hiçbir tetikleme yapmana gerek yok:

  1. POST /products veya PUT /products/{id} ile ürünü besle.
  2. Portal'da tanımlı yayın kuralları (PublishRule) match eden ürünleri hedef pazaryerlerine atar.
  3. Sistem job'ları kuyrukta yayını yapar; throttle, retry, batch birleştirme, idempotency bizim sorumluluğumuzdur.
  4. "Bu ürüne hangi pazaryerine ne fiyat/stok gider?" sorusuna effective-targets cevap verir. Yayın sonucu/geçmişi portal panelindedir (public API'de değil).

Manuel yayın tetiği API'de yokturpublish, bulk-publish, publish-all portal panelinden çalışır. Sebep: pazaryeri quota'sı ve throttle koruması bizim hesabımıza yazılır; harici tetik akışları (cron, yanlış senkronizasyon refleksi) komşu hasarına neden olur. İlk migration / drift düzeltme için panel kullanılır; programatik re-publish ihtiyacı çıkarsa cooldown'lı, idempotent bir endpoint olarak ayrıca planlanır.

Audit & debug

Yayın hedefleri — GET /products/{id}/effective-targets

"Bu ürünü publish etsem, hangi pazaryerine, hangi fiyat ve stok ile gider?" sorusunun cevabı. Yayın kuralları, mapping override'ları ve mağaza bazlı stok dağılımını birleştirir.

http
GET /v1/products/1001/effective-targets
Authorization: Bearer <API_KEY>

Response — 200 OK:

json
{
  "data": [
    {
      "credential_id": 5,
      "platform": "trendyol-go",
      "platform_label": "Trendyol Go",
      "label": "Mağazam TGo",
      "excluded": false,
      "resolved_price": 360.00,
      "resolved_stock": 50,
      "stores": [
        {"label": "İstanbul Mağaza", "platform_store_id": "1001", "code": "TR-IST-01", "quantity": 20},
        {"label": "Ankara Mağaza",   "platform_store_id": "1002", "code": "TR-ANK-01", "quantity": 15}
      ],
      "mapping": {"sync_id": "TGO-789"},
      "channel": {
        "override_price": null,
        "override_markup_percent": null,
        "override_stock": null,
        "is_excluded": false,
        "override_source": null
      }
    },
    {
      "credential_id": 7,
      "platform": "trendyol",
      "platform_label": "Trendyol",
      "label": "Mağazam Trendyol",
      "excluded": true,
      "resolved_price": null,
      "resolved_stock": null,
      "stores": [],
      "mapping": null,
      "channel": null
    }
  ]
}
  • excluded: true → yayın kuralları bu ürünü o pazaryerinden dışlamış (resolved alanları null).
  • mapping → eşleme varsa pazaryeri tarafındaki kimlik ({sync_id}); yoksa null.
  • channel → o pazaryerine özel override'lar (override_price, override_markup_percent, override_stock, is_excluded, override_source); override yoksa null.
  • stores → her mağaza için gönderilecek stok. code boşsa resolved_stock kullanılır.

Yayın geçmişi / denemeleri (hangi denemenin ne zaman, hangi sonuçla gittiği) public API'de değildir — pazaryeri yayını bizim tarafımızda yönetilir. Bu detay portal panelinde görüntülenir.

Hata kodları (özet)

HTTPSenaryo
401Token eksik/geçersiz
404id bulunamadı
409Mapping çakışması
422Validation: name boş, sku duplicate, tax_rate geçersiz, brand_id yok vs.
500Sunucu hatası (örn. resim diske yazılamadı)

Detaylı: errors.

Entegrasyoner — Pazaryeri Entegrasyon Sistemi