# Hatalar ve Rate Limit'ler

Storefront API'si hataları alışılagelmiş HTTP durum kodları ve küçük JSON body'lerle bildirir. Bu sayfa API'nin ürettiği her hata zarfını kataloglar, doğrulama hatası şeklini açıklar, rate limit'i belgeler ve tüm liste endpoint'lerinin kullandığı sayfalama zarfını tanımlar. Endpoint referans sayfaları bu şekilleri tekrarlamak yerine buraya bağlantı verir.

Tüm hata body'leri JSON nesneleridir. Çoğu bir `message` alanı taşır; birkaç özel durum farklıdır ve aşağıda belirtilmiştir: modül-devre-dışı hatası `error` anahtarı kullanır; askıya alma hatası, reddedilen sepet satırları, sepet ve checkout politikası ihlalleri ve bazı teklif token retleri makine tarafından okunabilir bir `code` ekler.

## 401 Unauthenticated

`Authorization: Bearer {api_key}` header'ı eksik, bozuk veya iptal edilmiş bir anahtar taşıyor olduğunda döndürülür. Header `Bearer` dışında bir şema kullandığında da döndürülür.

```json title="Response"
{
  "message": "Unauthenticated."
}
```

Çözüm: geçerli, iptal edilmemiş bir API anahtarını `Authorization: Bearer {api_key}` olarak gönderin. Anahtarı `GET /v1/api/me` ile doğrulayın.

## 403 Forbidden

Üç ayrı durum 403 döndürür. Bunları body şeklinden ayırt edin.

### Tenant askıya alınmış

Anahtarın ait olduğu mağaza Borkol tarafından askıya alınmış. Askı kaldırılana kadar her `/v1/api` isteği bu body ile başarısız olur. `code` alanı sabittir; `code === "tenant_suspended"` üzerinden dallanın.

```json title="Response"
{
  "message": "Tenant suspended.",
  "code": "tenant_suspended"
}
```

### Eksik scope

Anahtar geçerli ancak endpoint'in gerektirdiği scope'u taşımıyor (örneğin yalnızca `commerce:read` scope'una sahip bir anahtarla bir `commerce:write` endpoint'ini çağırmak).

```json title="Response"
{
  "message": "Invalid ability provided."
}
```

Çözüm: client dashboard'da (Settings > API keys) gerekli scope'a sahip bir anahtar oluşturun. Mevcut bir anahtara scope eklenemez.

### Modül etkin değil

Rota, mağaza için etkinleştirilmemiş bir modüle ait. Bu hata `message` değil `error` anahtarı kullanır ve scope kontrolüne baskındır: anahtar modülün scope'unu taşısa bile döndürülür. `{key}` modül anahtarıdır: `catalog`, `commerce`, `configurator`, `sales`, `stock`, `objects` veya `forms`.

Gövde ayrıca `code: "module_disabled"` ve `module` (yalnızca modül anahtarı) taşır; böylece bir istemci `error` alanını ayrıştırmak yerine `code` üzerinden dallanabilir. `error` değişmez.

Bir rota bu gövdeyi 403 yerine 404 durumuyla döndürür: sales modülü olmadan `POST /v1/api/commerce/carts/{id}/quote-request`, `404 {"error":"Module not enabled: sales"}` döndürür; bu, mağaza ayarlarında kapatılmış teklif talepleriyle aynı durumdur, böylece bir storefront'un ele alması gereken tek bir "kullanılamaz" durumu olur.

```json title="Response"
{
  "error": "Module not enabled: commerce",
  "code": "module_disabled",
  "module": "commerce"
}
```

Çözüm: modül etkinliği mağaza başına Borkol tarafından yönetilir; modülü etkinleştirmek için Borkol ile iletişime geçin.

## 404 Not Found

Bilinmeyen rotalar ve mağaza içinde mevcut bir kayda çözümlenmeyen path parametreleri için döndürülür. Body `{"message": "..."}` şeklindedir; başarısız bir model aramasında mesaj Laravel'in varsayılanıdır, örneğin:

```json title="Response"
{
  "message": "No query results for model [Borkol\\Catalog\\Models\\Product] 9c4e7f0a-2b31-4d6e-8f0a-1d2c3b4a5e6f"
}
```

Başka bir mağazaya ait kayıtlar, var olmayan kayıtlardan ayırt edilemez: her ikisi de 404 döndürür, asla 403 döndürmez.

## 422 Doğrulama hatası

Bir request body veya query parametresi doğrulamadan geçemediğinde döndürülür. Şekil her zaman şudur:

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `message` | string | her zaman | İlk doğrulama hatası mesajı (insan tarafından okunabilir bir özet). |
| `errors` | object | her zaman | Alan adından, o alan için bir veya daha fazla hata mesajı string'i içeren bir diziye eşleme. İç içe alanlar nokta gösterimi kullanır (örneğin `lines.0.quantity`). |

```json title="Response"
{
  "message": "The email field is required.",
  "errors": {
    "email": ["The email field is required."],
    "lines.0.quantity": ["The lines.0.quantity must be greater than zero."]
  }
}
```

Her endpoint'in referans sayfası alanlarını türleri, zorunluluk işaretleri ve doğrulama kurallarıyla listeler; 422 body'si her zaman bu zarfı izler.

### Sepet satırı ret kodu

`POST /v1/api/commerce/carts/{id}/lines` bir ürünü reddettiğinde 422 body'si, `message` ve `errors` alanlarının yanına en üst düzeyde bir `code` ekler:

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `code` | string | koşullu | `CAPABILITY_INACTIVE` (ürünün capability'sini satan modül devre dışı), `INVALID_CONFIGURATION` (varyant veya yapılandırma ürüne uymuyor) veya `NOT_PURCHASABLE` (ürün satılamaz). Düz girdi doğrulama hatalarında bulunmaz. |

```json title="Response"
{
  "message": "Product is not available.",
  "errors": {
    "product_id": ["Product is not available."]
  },
  "code": "CAPABILITY_INACTIVE"
}
```

`code` enum'unu açık kabul edin. Konfigüratörün quote endpoint'i yapılandırma hatalarını `code` olmadan düz zarfta bildirir. Bkz. [Sepet satırları](/tr/api/carts-and-checkout/cart-lines).

## 429 Too Many Attempts (rate limiting)

Tüm `/v1/api` rotaları, **mağaza başına dakikada 120 istek** olan tek bir rate limit'i paylaşır. Limit API anahtarına veya IP'ye göre değil, mağazaya (tenant'a) göre anahtarlanır: bir mağazanın tüm anahtarları aynı bütçeden harcar. Rate limit'li rotalara yapılan kimlik doğrulamasız istekler istemci IP'sine göre anahtarlanır. `GET /v1/ping` bu limite tabi değildir.

Rate limit'li bir rotadaki her yanıt şunları içerir:

| Header | Açıklama |
| --- | --- |
| `X-RateLimit-Limit` | Dakikalık bütçe (120). |
| `X-RateLimit-Remaining` | Geçerli pencerede kalan istek sayısı. |

Bütçe tükendiğinde API, `Retry-After` header'ıyla (pencerenin sıfırlanmasına kalan saniye) birlikte HTTP 429 döndürür:

```json title="Response"
{
  "message": "Too Many Attempts."
}
```

İstemciler `X-RateLimit-Remaining` değerini izlemeli, sıfıra ulaşmadan yavaşlamalı ve 429'da `Retry-After` değerine uymalıdır. Katalog ağırlıklı storefront'larda her sayfa görüntülemeyi API'ye iletmek yerine okumaları kendi tarafınızda önbelleğe alın.

Form başvurularının ayrıca kendi sınırı vardır: her form, satıcının belirlediği sayıda ziyaretçi başına saatlik başvuru kabul eder. Bunun üzerinde `POST /v1/api/forms/{key}/submissions`, `Retry-After` header'ı olmadan `429 {"message": "Too many submissions. Try again later."}` döndürür. Bkz. [Başvurular](/tr/api/forms/submissions). Form dosya yüklemeleri form ve ziyaretçi başına saatte 30 ile sınırlıdır: bunun üzerinde `POST /v1/api/forms/{key}/uploads`, yine `Retry-After` olmadan `429 {"message": "Too many uploads. Try again later."}` döndürür. Bkz. [Yüklemeler](/tr/api/forms/uploads).

## Sayfalama zarfı

Storefront API'sindeki her liste endpoint'i aynı zarfı döndürür: bir `data` dizisi artı `links` ve `meta` nesneleri. Bu, burada bir kez belgelenmiştir; endpoint sayfaları yalnızca `data` içindeki nesnelerin şeklini açıklar.

Liste endpoint'leri şu query parametrelerini kabul eder:

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `page` | integer | Hayır | Sayfa numarası, 1 tabanlı. Varsayılan 1. |
| `per_page` | integer | Hayır | Sayfa başına öğe sayısı. Her endpoint kendi varsayılanını ve maksimumunu belgeler (genellikle varsayılan 25, maksimum 100); izin verilen aralığın dışındaki değerler aralığa sıkıştırılır. |

Zarf:

| Ad | Tür | Açıklama |
| --- | --- | --- |
| `data` | array | Kaynakların bu sayfası. |
| `links.first` | string | İlk sayfanın URL'i. |
| `links.last` | string | Son sayfanın URL'i. |
| `links.prev` | string or null | Önceki sayfanın URL'i, ilk sayfada null. |
| `links.next` | string or null | Sonraki sayfanın URL'i, son sayfada null. |
| `meta.current_page` | integer | Geçerli sayfa numarası. |
| `meta.from` | integer or null | Bu sayfadaki ilk öğenin 1 tabanlı indeksi, sayfa boşken null. |
| `meta.last_page` | integer | Toplam sayfa sayısı. |
| `meta.links` | array | Sayfa kontrolleri oluşturmak için paginator bağlantı nesneleri (`url`, `label`, `active`). |
| `meta.path` | string | Endpoint'in sayfalama parametreleri olmadan base URL'i. |
| `meta.per_page` | integer | Etkin sayfa boyutu. |
| `meta.to` | integer or null | Bu sayfadaki son öğenin 1 tabanlı indeksi, sayfa boşken null. |
| `meta.total` | integer | Eşleşen toplam öğe sayısı. |

```json title="Response"
{
  "data": [
    { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" }
  ],
  "links": {
    "first": "https://api.borkol.com/v1/api/commerce/products?page=1",
    "last": "https://api.borkol.com/v1/api/commerce/products?page=4",
    "prev": null,
    "next": "https://api.borkol.com/v1/api/commerce/products?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 4,
    "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": 25,
    "to": 25,
    "total": 87
  }
}
```

Tek bir nesne döndüren endpoint'ler (örneğin `GET /v1/api/me`), referans sayfalarında aksi belirtilmedikçe `data` sarmalayıcısı olmadan düz JSON nesnesini döndürür.

## İstemciler için hata işleme özeti

| Durum | Nasıl ayırt edilir | Yapılacak işlem |
| --- | --- | --- |
| 401 | `message == "Unauthenticated."` | Anahtar eksik veya iptal edilmiş; geçerli bir anahtar edinin. Yeniden denenemez. |
| 403 | `code == "tenant_suspended"` | Mağaza askıya alınmış; sorun çözülene kadar çağrı yapmayı durdurun. |
| 403 | `message == "Invalid ability provided."` | Anahtar scope'tan yoksun; doğru scope'lara sahip bir anahtar oluşturun. Yeniden denenemez. |
| 403 | `code == "module_disabled"` (veya `"Module not enabled:"` ile başlayan `error`) | Modül mağaza için devre dışı. Yeniden denenemez. |
| 404 | `message` mevcut veya `"Module not enabled:"` ile başlayan `error` var | Rota veya kayıt bu mağazada mevcut değil ya da (yalnızca teklif talebi) sales modülü devre dışı. |
| 422 | `errors` ile birlikte `code` değeri `CAPABILITY_INACTIVE`, `INVALID_CONFIGURATION` veya `NOT_PURCHASABLE` | Sepet satırı reddedildi; `code` alanına göre dallanın, `errors` alanını gösterin. |
| 422 | `code` mevcut, `errors` yok | Politika veya iş kuralı reddi; `message` alanını gösterin. Kodlar: `cart_constraint` (sepet satırları), `login_required`, `guest_only`, `b2b_approval_required`, `b2c_only`, `min_order_value` (checkout), `SIGNATURE_REQUIRED`, `SIGNATURE_INVALID` (teklif kabulü). |
| 422 | `errors` nesnesi mevcut | Listelenen alanları düzeltip yeniden gönderin. |
| 429 | `message == "Too Many Attempts."` | `Retry-After` saniye bekleyin, sonra yeniden deneyin. |
| 429 | `message == "Too many submissions. Try again later."` | Bu ziyaretçi için form başvurusu sınırı; ziyaretçiden daha sonra tekrar denemesini isteyin. |
| 429 | `message == "Too many uploads. Try again later."` | Bu ziyaretçi için form yükleme sınırı; ziyaretçiden daha sonra tekrar denemesini isteyin. |
| 5xx | herhangi biri | Sunucu tarafı hata; exponential backoff ile yeniden deneyin. |
