# Ürünler

Ürün endpoint'leri, kimliği doğrulanmış tenant'ın aktif ürünlerini döndürür. Her iki endpoint de `Authorization: Bearer {api_key}` olarak gönderilen, `catalog:read` veya `commerce:read` scope'una sahip bir tenant API anahtarı ve mağaza için etkinleştirilmiş catalog modülü gerektirir. Tenancy anahtardan türetildiği için `X-Tenant-Id` header'ına gerek yoktur. İstekler, dakikada 120 istek olan ortak tenant rate limit'inden düşer; limiti aşmak, `Retry-After` ve `X-RateLimit-*` header'larıyla birlikte `429 {"message":"Too Many Attempts."}` döndürür.

Yalnızca durumu `active` olan ürünler döndürülür. Taslak, arşivlenmiş ve soft-delete edilmiş ürünler listeden filtrelenir ve detay endpoint'inde 404 döndürür.

## Ürün nesnesi

Tüm ID'ler UUID'dir. Tüm para tutarları, ürünün ISO para birimi koduyla eşleştirilmiş küçük birim (kuruş/cent) cinsinden integer'lardır (örneğin `"EUR"` ile `18900`, 189,00 EUR demektir). Zaman damgaları ISO 8601 UTC'dir.

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| id | string (uuid) | her zaman | Ürün ID'si. |
| capability | string | her zaman | Ürünün nasıl satıldığı: `standard` (sepete `product_id` ve varyantı varsa `variant_id` ile eklenen düz veya varyantlı ürün) ya da `made_to_measure` (configurator ile fiyatlanır, sepete bir `configuration` ile eklenir). Enum'u açık kabul edin ve bilinmeyen bir değeri nötr gösterin. |
| capability_active | boolean | her zaman | Ürünün capability'sinin şu anda satılıp satılamayacağı: `standard` için commerce modülü, `made_to_measure` için configurator modülü gerekir. `false` olduğunda sepete ekleme ürünü `CAPABILITY_INACTIVE` ile reddeder; satın al düğmesini gizleyin. |
| purchasability | string | her zaman | Şunlardan biri: `direct`, `quote`, `both`; merchant hiçbir şey ayarlamadıysa `direct`. `quote`, ürünün doğrudan satın alınamayacağı anlamına gelir; alıcıyı bir teklif talebine yönlendirin. Alan, mağazada sales modülü devre dışı olsa bile raporlanır; ancak ödemeyi yalnızca sales modülü etkin (ve teklif talepleri açık) bir mağaza reddeder. |
| unit | string | her zaman | Ürünün satıldığı birim: `piece`, `kg`, `g`, `l`, `m`, `m2`, `m3` veya `hour`. `piece` ürünü yalnızca tam sayı sepet miktarı kabul eder; diğer tüm birimler en fazla üç ondalık basamak kabul eder (örneğin `0.75` kg). |
| sku | string or null | her zaman | Ürün düzeyinde SKU. |
| slug | string | her zaman | URL slug'ı, tenant başına benzersiz. |
| name | string | her zaman | Ürün adı. |
| description | string or null | her zaman | Ürün açıklaması. |
| status | string | her zaman | Bu API'de her zaman `active`. |
| meta_title | string or null | her zaman | SEO başlığı. |
| meta_description | string or null | her zaman | SEO açıklaması. |
| compare_at_amount | integer or null | her zaman | Merchant'ın belirlediği karşılaştırma (eski) fiyatı, küçük birim cinsinden; her zaman ürün fiyatından yüksektir. Ayarlanmamışsa `null`. Müşteri grubu veya kademe fiyatlandırmasından önceki fiyatı bildiren `list_price_amount` alanından bağımsızdır. |
| barcode | string or null | her zaman | Barkod (EAN, UPC veya merchant'a ait herhangi bir kod). |
| weight_grams | integer or null | her zaman | Gram cinsinden gönderim ağırlığı. |
| requires_shipping | boolean | her zaman | Ürünün fiziksel olarak gönderilip gönderilmediği. Bir sepet, ödemede yalnızca ürünü gönderim gerektiren en az bir satır içerdiğinde gönderim yöntemine ihtiyaç duyar. |
| track_stock | boolean | her zaman | Mağazanın bu ürün için stok takibi yapıp yapmadığı. `false` olduğunda `stock_status` `untracked` olur ve ürün hiçbir zaman tükenmez. |
| returnable | boolean | her zaman | Merchant'ın iadeyi kabul edip etmediği. `made_to_measure` ürünler siparişe göre üretilir ve bu bayrak ne derse desin sipariş verildikten sonra asla iade edilemez (sipariş satırındaki `attributes.returnable` alanına bakın). |
| lead_time_days | integer or null | her zaman | Gün cinsinden üretim veya teslim süresi, 0 ile 365 arası. Ayarlanmamışsa `null`. |
| tags | array of string | her zaman | Merchant etiketleri. Yoksa boş array. |
| social_image | object or null | her zaman | Sosyal paylaşım (Open Graph) görseli; aşağıdaki görsel nesnesine ek olarak `id` alanını içerir. Ayarlanmamışsa `null`; bu durumda `image` alanını kullanın. |
| price | object | her zaman | `{amount, currency}`. `amount`, isteğin müşteri bağlamı için (herhangi bir `X-Customer-Token` header'ı gönderilmediğinde misafir) miktar 1'deki çözümlenmiş birim fiyattır. Tenant fiyatları misafirlerden gizliyorsa ve istek bir misafir isteğiyse `amount` `null` olur. |
| list_price_amount | integer | koşullu | Küçük birim cinsinden orijinal (liste) fiyatı. Yalnızca çözümlenen fiyattan farklı olduğunda ve tenant orijinal fiyatları gösterdiğinde bulunur. |
| quantity_tiers | array | koşullu | `[{min_quantity, price_amount}]` satırları; yalnızca tenant kademe tablolarını gösterdiğinde ve `min_quantity > 1` olan ürün kapsamlı kademeler mevcut olduğunda bulunur. Müşteri grubu satırları, aynı `min_quantity` değerindeki herkese açık satırları gölgeler. |
| price_display | object | koşullu | `{mode}`; `mode` `inc` veya `ex`'tir (KDV dahil veya KDV hariç gösterim). Tenant'ın `vat_display` ayarından çözümlenir (`inc`, `ex` veya `auto`; `auto`, `X-Customer-Token` ile tanımlanan B2B müşteriler için `ex`, diğer durumlarda `inc` olarak çözümlenir). |
| attributes | object or array | her zaman | Merchant tarafından belirlenen serbest biçimli öznitelik haritası. Boşken `[]` olarak serileştirilir. |
| options | array | yalnızca detay | `[{name, values}]`, ürünün seçenek tanımları (örneğin `{"name":"Color","values":["Natural","Black"]}`). Yalnızca `GET /v1/api/commerce/products/{id}` yanıtında yer alır. |
| categories | array | her zaman | Ürünün ait olduğu aktif kategoriler: `[{id, name, slug, is_active}]`. |
| variants | array | her zaman | Aktif varyantlar, aşağıdaki varyant nesnesine bakın. Ürünün varyantı yoksa boş dizi. |
| has_variants | boolean | her zaman | `variants` dizisinin boş olup olmadığı. |
| price_from | object | her zaman | `{amount, currency}`. Çözümlenmiş en düşük varyant fiyatı veya varyant yoksa ürün fiyatı. Tenant misafir fiyatlarını gizliyorsa misafirler için `amount` `null` olur. |
| image | object or null | her zaman | Birincil görsel (primary olarak işaretlenmiş medya öğesi, yoksa ilki), aşağıdaki görsel nesnesine bakın. Ürünün medyası yoksa `null`. |
| images | array | yalnızca detay | Tam galeri; her öğe bir görsel nesnesi artı `id`, `is_primary` (boolean) ve `sort_order` (integer) içerir. Yalnızca `GET /v1/api/commerce/products/{id}` yanıtında yer alır. |
| stock_status | string | koşullu | Şunlardan biri: `in_stock`, `low_stock`, `out_of_stock`, `backorder`, `untracked`. `untracked`, mağazanın bu ürünü saymadığı anlamına gelir: `track_stock` `false`, stok satırı yok veya mağazada stock modülü devre dışı. Stock modülü platformda kayıtlı değilse tamamen atlanır. |
| created_at | string (ISO 8601) | her zaman | Oluşturulma zaman damgası. |
| updated_at | string (ISO 8601) | her zaman | Son güncelleme zaman damgası. |

Client panelindeki `stock_available` alanı bu API'de asla yer almaz.

### Varyant nesnesi

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| id | string (uuid) | her zaman | Varyant ID'si. |
| option_values | object | her zaman | Seçenek adından seçilen değere eşleme, örneğin `{"Color":"Natural"}`. |
| sku | string or null | her zaman | Varyant SKU'su. |
| barcode | string or null | her zaman | Varyant barkodu. |
| weight_grams | integer or null | her zaman | Gram cinsinden varyant gönderim ağırlığı; `null`, ürünün `weight_grams` değerinin geçerli olduğu anlamına gelir. |
| price | object | her zaman | `{amount, currency}`. Varyantın kendi fiyatı yoksa ürün fiyatına döner. Tenant misafir fiyatlarını gizliyorsa misafirler için `amount` `null` olur. |
| is_active | boolean | her zaman | Bu API'de her zaman `true` (pasif varyantlar filtrelenir). |
| image_id | string (uuid) veya null | her zaman | Varyantın kendi görseli: ürünün medya öğelerinden birinin `id` değeri; bu nedenle detay endpoint'indeki `images` içindeki bir öğeyle eşleşir. Müşteri bu varyantı seçtiğinde gösterin. Varyantın kendi görseli yoksa `null`; bu durumda ürünün `image` alanını kullanın. |
| image | object veya null | her zaman | Varyantın kendi görseli, `id` olmadan bir görsel nesnesi olarak (aşağıya bakın). Varyantın kendi görseli yoksa `null`. |
| stock_status | string | koşullu | Ürün düzeyindeki `stock_status` ile aynı enum; `track_stock` değeri `false` olan bir ürünün tüm varyantları `untracked` olur. Yalnızca stock modülü kayıtlıyken bulunur. |

### Görsel nesnesi

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| url | string | her zaman | Orijinal görsel URL'i. |
| variants | object | her zaman | wsrv.nl üzerinden sunulan, `thumb` (160px genişlik), `card` (480px genişlik), `detail` (1200px genişlik) anahtarlı, anında oluşturulan webp yeniden boyutlandırma URL'leri. |
| alt | string or null | her zaman | Alt metni. |

## GET /v1/api/commerce/products

Tenant'ın aktif ürünlerinin sayfalanmış bir listesini, en yeniden başlayarak döndürür (`created_at` azalan, yapılandırılamaz). Her ürün; birincil görselini, aktif varyantlarını, aktif kategorilerini, stok durumunu (stock modülü kayıtlıyken) ve müşteri bağlamına göre fiyatlandırmayı içerir. Liste, `options` dizisini ve `images` galerisini içermez; bunlar için detay endpoint'ini çağırın. Sayfa boyutu 20'de sabittir; `per_page`, sıralama veya arama parametresi yoktur.

### Query parametreleri

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| filter[category] | string | Hayır | Kategori slug'ı. O aktif kategorideki veya aktif alt kategorilerinin herhangi birindeki ürünleri eşleştirir. Bilinmeyen veya pasif bir slug boş bir sonuç kümesi verir (200, 404 değil). |
| page | integer | Hayır | Sayfa numarası, varsayılan 1. Sayfa boyutu 20'de sabittir. |

### Header'lar

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| X-Customer-Token | string | Hayır | Storefront müşteri oturum token'ı. Fiyatlandırmayı o müşterinin bağlamına geçirir (grup fiyatlandırması, B2B KDV hariç gösterim, kademe tabloları). Onsuz fiyatlandırma misafir için hesaplanır. |

```bash title="Request"
curl "https://api.borkol.com/v1/api/commerce/products?filter%5Bcategory%5D=tables&page=1" \
  -H "Authorization: Bearer {api_key}" \
  -H "Accept: application/json"
```

```javascript title="Request (JS)"
const res = await fetch(
  "https://api.borkol.com/v1/api/commerce/products?" +
    new URLSearchParams({ "filter[category]": "tables", page: "1" }),
  {
    headers: {
      Authorization: `Bearer ${apiKey}`,
      Accept: "application/json",
    },
  }
);
const { data, links, meta } = await res.json();
```

```json title="Response"
{
  "data": [
    {
      "id": "9c2f1e6a-8b4d-4f2a-9e11-3d5a7b9c0e21",
      "capability": "standard",
      "capability_active": true,
      "purchasability": "direct",
      "unit": "piece",
      "sku": "TABLE-OAK",
      "slug": "oak-table",
      "name": "Oak Table",
      "description": "Solid oak table.",
      "status": "active",
      "meta_title": null,
      "meta_description": null,
      "compare_at_amount": null,
      "barcode": null,
      "weight_grams": 32000,
      "requires_shipping": true,
      "track_stock": true,
      "returnable": true,
      "lead_time_days": null,
      "tags": [],
      "social_image": null,
      "price": { "amount": 54900, "currency": "EUR" },
      "price_display": { "mode": "inc" },
      "attributes": [],
      "categories": [
        {
          "id": "7a1b3c5d-2e4f-4a6b-8c0d-1e2f3a4b5c6d",
          "name": "Tables",
          "slug": "tables",
          "is_active": true
        }
      ],
      "variants": [],
      "has_variants": false,
      "price_from": { "amount": 54900, "currency": "EUR" },
      "image": {
        "url": "https://cdn.borkol.com/t/9c2f/table.jpg",
        "variants": {
          "thumb": "https://wsrv.nl/?url=...&w=160&we=1&output=webp&q=80",
          "card": "https://wsrv.nl/?url=...&w=480&we=1&output=webp&q=80",
          "detail": "https://wsrv.nl/?url=...&w=1200&we=1&output=webp&q=80"
        },
        "alt": "Oak table"
      },
      "stock_status": "in_stock",
      "created_at": "2026-07-02T09:14:33.000000Z",
      "updated_at": "2026-08-01T15:20:11.000000Z"
    }
  ],
  "links": {
    "first": "https://api.borkol.com/v1/api/commerce/products?page=1",
    "last": "https://api.borkol.com/v1/api/commerce/products?page=3",
    "prev": null,
    "next": "https://api.borkol.com/v1/api/commerce/products?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 3,
    "links": [
      { "url": null, "label": "&laquo; Previous", "active": false },
      {
        "url": "https://api.borkol.com/v1/api/commerce/products?page=1",
        "label": "1",
        "active": true
      }
    ],
    "path": "https://api.borkol.com/v1/api/commerce/products",
    "per_page": 20,
    "to": 20,
    "total": 47
  }
}
```

Zarf, standart Laravel resource paginator'ıdır: üst düzey bir `data` dizisi artı `links` (`first`, `last`, `prev`, `next`, mutlak URL'ler veya `null`) ve `meta` (`current_page`, `from`, `last_page`, `links`, `path`, `per_page`, `to`, `total`). `per_page` her zaman 20'dir.

### Hatalar

| Durum | Body | Neden |
| --- | --- | --- |
| 401 | `{"message":"Unauthenticated."}` | API anahtarı eksik veya geçersiz. |
| 403 | `{"message":"Invalid ability provided."}` | Anahtar ne `catalog:read` ne de `commerce:read` scope'una sahip. |
| 403 | `{"error":"Module not enabled: catalog"}` | Tenant'ta catalog modülü etkin değil. `message` yerine `error` anahtarına dikkat edin. |
| 403 | `{"message":"Tenant suspended.","code":"tenant_suspended"}` | Tenant hesabı askıya alınmış. |
| 429 | `{"message":"Too Many Attempts."}` | Dakikada 120 istek rate limit'i aşıldı. |

## GET /v1/api/commerce/products/{id}

UUID ile tek bir aktif ürünü getirir. Liste endpoint'ine kıyasla detay yanıtı ek olarak `options` dizisini ve tam `images` galerisini (`is_primary` ve `sort_order` ile birlikte) içerir. Stok durumu ve fiyatlandırma listedeki gibi zenginleştirilir: `X-Customer-Token` header'ı yoksa misafir fiyatlandırması uygulanır. Taslak, arşivlenmiş, soft-delete edilmiş veya bilinmeyen ürünler için 404 döndürür.

### Path parametreleri

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| id | string (uuid) | Evet | Ürün ID'si. |

### Header'lar

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| X-Customer-Token | string | Hayır | Storefront müşteri oturum token'ı. Fiyatlandırmayı o müşterinin grubuna ve B2B bağlamına geçirir. |

```bash title="Request"
curl "https://api.borkol.com/v1/api/commerce/products/9c2f1e6a-8b4d-4f2a-9e11-3d5a7b9c0e21" \
  -H "Authorization: Bearer {api_key}" \
  -H "Accept: application/json"
```

```json title="Response"
{
  "data": {
    "id": "9c2f1e6a-8b4d-4f2a-9e11-3d5a7b9c0e21",
    "capability": "standard",
    "capability_active": true,
    "purchasability": "direct",
    "unit": "piece",
    "sku": "CHAIR-OAK",
    "slug": "oak-dining-chair",
    "name": "Oak Dining Chair",
    "description": "Solid oak chair with linen seat.",
    "status": "active",
    "meta_title": "Oak Dining Chair",
    "meta_description": "Handmade solid oak dining chair.",
    "compare_at_amount": 22900,
    "barcode": "8712345678906",
    "weight_grams": 6500,
    "requires_shipping": true,
    "track_stock": true,
    "returnable": true,
    "lead_time_days": 5,
    "tags": ["oak", "dining"],
    "social_image": {
      "id": "3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
      "url": "https://cdn.borkol.com/t/9c2f/chair-social.jpg",
      "variants": {
        "thumb": "https://wsrv.nl/?url=...&w=160&we=1&output=webp&q=80",
        "card": "https://wsrv.nl/?url=...&w=480&we=1&output=webp&q=80",
        "detail": "https://wsrv.nl/?url=...&w=1200&we=1&output=webp&q=80"
      },
      "alt": "Oak dining chair in a dining room"
    },
    "price": { "amount": 18900, "currency": "EUR" },
    "list_price_amount": 21900,
    "quantity_tiers": [
      { "min_quantity": 5, "price_amount": 17900 },
      { "min_quantity": 10, "price_amount": 16900 }
    ],
    "price_display": { "mode": "inc" },
    "attributes": { "material": "oak" },
    "options": [
      { "name": "Color", "values": ["Natural", "Black"] }
    ],
    "categories": [
      {
        "id": "7a1b3c5d-2e4f-4a6b-8c0d-1e2f3a4b5c6d",
        "name": "Chairs",
        "slug": "chairs",
        "is_active": true
      }
    ],
    "variants": [
      {
        "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
        "option_values": { "Color": "Natural" },
        "sku": "CHAIR-OAK-NAT",
        "barcode": "8712345678913",
        "weight_grams": null,
        "price": { "amount": 18900, "currency": "EUR" },
        "is_active": true,
        "image_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
        "image": {
          "url": "https://cdn.borkol.com/t/9c2f/chair.jpg",
          "variants": {
            "thumb": "https://wsrv.nl/?url=...&w=160&we=1&output=webp&q=80",
            "card": "https://wsrv.nl/?url=...&w=480&we=1&output=webp&q=80",
            "detail": "https://wsrv.nl/?url=...&w=1200&we=1&output=webp&q=80"
          },
          "alt": "Oak dining chair front view"
        },
        "stock_status": "in_stock"
      }
    ],
    "has_variants": true,
    "price_from": { "amount": 18900, "currency": "EUR" },
    "image": {
      "url": "https://cdn.borkol.com/t/9c2f/chair.jpg",
      "variants": {
        "thumb": "https://wsrv.nl/?url=...&w=160&we=1&output=webp&q=80",
        "card": "https://wsrv.nl/?url=...&w=480&we=1&output=webp&q=80",
        "detail": "https://wsrv.nl/?url=...&w=1200&we=1&output=webp&q=80"
      },
      "alt": "Oak dining chair front view"
    },
    "images": [
      {
        "id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
        "url": "https://cdn.borkol.com/t/9c2f/chair.jpg",
        "variants": {
          "thumb": "https://wsrv.nl/?url=...&w=160&we=1&output=webp&q=80",
          "card": "https://wsrv.nl/?url=...&w=480&we=1&output=webp&q=80",
          "detail": "https://wsrv.nl/?url=...&w=1200&we=1&output=webp&q=80"
        },
        "alt": "Oak dining chair front view",
        "is_primary": true,
        "sort_order": 0
      }
    ],
    "stock_status": "in_stock",
    "created_at": "2026-07-02T09:14:33.000000Z",
    "updated_at": "2026-08-01T15:20:11.000000Z"
  }
}
```

### Hatalar

| Durum | Body | Neden |
| --- | --- | --- |
| 401 | `{"message":"Unauthenticated."}` | API anahtarı eksik veya geçersiz. |
| 403 | `{"message":"Invalid ability provided."}` | Anahtar ne `catalog:read` ne de `commerce:read` scope'una sahip. |
| 403 | `{"error":"Module not enabled: catalog"}` | Tenant'ta catalog modülü etkin değil. |
| 403 | `{"message":"Tenant suspended.","code":"tenant_suspended"}` | Tenant hesabı askıya alınmış. |
| 404 | `{"message":"..."}` | Ürün mevcut değil, soft-delete edilmiş veya durumu `active` değil. |
| 429 | `{"message":"Too Many Attempts."}` | Dakikada 120 istek rate limit'i aşıldı. |
