Appearance
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
GET /products— listele (paginate + filter)GET /products/{id}— tek ürün detayPOST /products— yeni ürün eklePUT /products/{id}— mevcut ürünü güncelle (partial)DELETE /products/{id}— ürünü sil
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
GET /products/{id}/effective-targets— bu ürün hangi pazaryerlerine, hangi fiyat/stok ile gider
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ı
| Alan | Tip | Açıklama |
|---|---|---|
name | string (POST'ta zorunlu) | Ürün adı |
model | string | Ürün kodu — lookup'ta stabil identifier |
sku | string | Stok kodu |
ean | string | Barkod |
description | string | HTML açıklama |
status | bool | true = aktif (pazaryerlerinde satışta) |
publish_enabled | bool | false = 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:
| Alan | Tip | Açıklama |
|---|---|---|
price | numeric | KDV-hariç fiyat |
price_with_vat | numeric | KDV-dahil fiyat — verilirse price otomatik hesaplanır (reverse-compute) |
tax_rate | numeric | KDV oranı yüzdesi (örn. 20) — sistemdeki TaxRate'e göre resolve edilir |
tax_rate_id | int | TaxRate FK ID (yüzde verilirse yüzde öncelikli) |
Öncelik mantığı:
tax_rate(yüzde) → varsa kullan, yoksatax_rate_idprice_with_vat > 0→ KDV-hariç price =price_with_vat / (1 + tax_rate/100)- Else
price > 0→ doğrudan kullan - İ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 }
]
}| Alan | Açıklama |
|---|---|
credential_id | Zorunlu. Hedef pazaryeri bağlantısı — GET /v1/channels'tan alınır. Override o bağlantının pazaryeri seviyesinde uygulanır. |
price | O pazaryerine giden mutlak fiyat (KDV-hariç). Markup/KDV bypass edilir. |
markup_percent | Mutlak yerine % override (master fiyat üzerine). price ile birlikte gönderilemez (422). |
stock | O pazaryerine özel stok (opsiyonel). |
- Çözüm önceliği:
price(mutlak) →markup_percent(%) → bağlantı/kategori markup'ı → bağlantı varsayılanı. nullgö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 seviyefirstOrCreate. 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
| Alan | Tip | Açıklama |
|---|---|---|
quantity | int | Ürünün genel stoğu (varsayılan lokasyon). Çoğu entegrasyon yalnızca bunu gönderir. |
attribute_bag | object<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 — sadecequantityyeterli.attribute_bagyalnı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;quantityburaya yazar. Her tenant'ta tek tanedir.marketplace_store— pazaryeri bağlantısıyla otomatik oluşur (örn. TrendyolGo'nun her şube mağazası).attribute_bagkey'leri tipik olarak bunlardır.external— manuel eklediğin ek lokasyon (POST /v1/stock-locationsiletype=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:
priceyerine"price_with_vat": 360yolla — 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.jpgKabul 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.
⚠️
namePUT'ta da gönderilmesi gerekir (model'inrequiredvalidation'ı). İ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/jsonQuery parametreleri:
search— name/sku/ean/model'da fragmented LIKEbrand_id,category_id,group_id— filtrestatus—true/falsehas_stock,has_image—true/falsemapped_platform_id,unmapped_platform_id— pazaryeri eşleme filtresihas_channel_override—true/false: kanala özel fiyat/stok override'ı olan ürünleroverride_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:
POST /productsveyaPUT /products/{id}ile ürünü besle.- Portal'da tanımlı yayın kuralları (
PublishRule) match eden ürünleri hedef pazaryerlerine atar. - Sistem job'ları kuyrukta yayını yapar; throttle, retry, batch birleştirme, idempotency bizim sorumluluğumuzdur.
- "Bu ürüne hangi pazaryerine ne fiyat/stok gider?" sorusuna
effective-targetscevap verir. Yayın sonucu/geçmişi portal panelindedir (public API'de değil).
Manuel yayın tetiği API'de yoktur — publish, 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}); yoksanull.channel→ o pazaryerine özel override'lar (override_price,override_markup_percent,override_stock,is_excluded,override_source); override yoksanull.stores→ her mağaza için gönderilecek stok.codeboşsaresolved_stockkullanı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)
| HTTP | Senaryo |
|---|---|
401 | Token eksik/geçersiz |
404 | id bulunamadı |
409 | Mapping çakışması |
422 | Validation: name boş, sku duplicate, tax_rate geçersiz, brand_id yok vs. |
500 | Sunucu hatası (örn. resim diske yazılamadı) |
Detaylı: errors.