# Sunucu Taraflı Render API

Basmaya hazır HTML döndüren sunucudan sunucuya endpoint
Kaynak: https://selwise.com/docs/api-reference/ssr-api

Sunucu taraflı render ucu, panelde hazırladığınız zenginleştirme içeriğini **basmaya hazır HTML** olarak döndürür. Kendi uygulama sunucunuzdan çağırır, çıktıyı şablonunuza olduğu gibi yazarsınız. Bu sayfa HTTP sözleşmesidir: kimlik, parametreler, yanıt, önbellek, sınırlar, hatalar.

- Ne olduğu, ne zaman kullanılacağı, mimari ve panel ayarı: [Sunucu Taraflı Render](/docs/product-enrichment/server-side)
- Kopyalanıp yapıştırılabilir PHP, Node, Next.js, Django örnekleri: [Sunucu Taraflı Render Örnekleri](/docs/product-enrichment/server-side-recipes)

## Neden hazır HTML

Diğer public uçlar ham veri döndürür ve işlemeyi widget yapar: yer tutucu doldurma, HTML temizleme, CSS kapsamlama. Sunucu tarafında bu üç adımı size yaptırmak üç ayrı güvenlik açığı davetiyesi olurdu: şablonda `|safe`, temizlenmemiş markup, kapsamlanmamış CSS. Bu yüzden uç son hâli döndürür: yer tutucular yerleşmiş, izin listesine göre temizlenmiş, CSS blok kapsamına alınmış. Widget'ın tarayıcıda, panel önizlemesinin ve bu ucun kullandığı işleme hattı aynıdır; panelde onayladığınız blok sayfada onayladığınız hâliyle çıkar.

> **Dönen HTML'i ikinci bir sanitizer'dan geçirmeyin**
>
> İçerik zaten temizlendi. Genel amaçlı bir sanitizer `data-selwise-*` niteliklerini siler; widget bloğu tanıyamaz, sayfada olduğunu bilmez ve **ikinci bir kopyasını basar**. Alışverişçi içeriği iki kez görür, arama motoru kopya içerik görür.

## Uç nokta

```http
GET /api/v1/public/sites/{siteKey}/ssr/enrichments
```

- **Kimlik:** `x-selwise-api-key`, her istekte (`ssr_read` kapsamı)
- **Katman:** Kararlı sözleşme

**Parametreler**

| Ad | Konum | Tip | Zorunlu | Açıklama |
| --- | --- | --- | --- | --- |
| `siteKey` | yol | `string` | Evet | Kanal anahtarı (Kanallar → Kanal Özeti → Kanal Anahtarı). Kanalı adlandırır; sır değildir. |
| `code` | sorgu | `string` | Hayır | Ürün kodu: `sku`, `item_code` ya da sepet kodu; büyük/küçük harf duyarsız, en çok 100 karakter. Boş ya da uzunsa hata değil boş yanıt döner. |
| `slots` | sorgu | `string` | Hayır | Yuva adları: virgülle ayrılmış (`a,b`) ya da tekrarlı parametre (`slots=a&slots=b`). Küçük harf, rakam ve tek tire; en çok 10, fazlası atılır. Verilmezse boş yanıt. |
| `lang` | sorgu | `string` | Hayır | Ziyaretçinin ya da sayfanın dili (`tr`, `en-GB`…). Hesabın dillerinden en yakını seçilir; yoksa varsayılan dil. Tek dilli hesapta etkisi yoktur. |

**Yanıt: 200 Başarılı**

| Alan | Tip | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `v` | `string` | Evet | İçerik sürüm damgası; ETag olarak da gelir. `If-None-Match` ile geri gönderirseniz değişmediyse 304 alırsınız. |
| `mode` | `string` | Evet | Şu an her zaman `anonymous`: yanıtta kişiye göre değişen bir şey yoktur, sayfa önbelleğine yazılabilir. · anonymous |
| `slots` | `map<string, SsrSlotSchema>` | Evet | Yuva adına göre basılmaya hazır HTML. İçeriği olmayan yuva boş döner. |
| `css` | `string` | Evet | Çizilen blokların kapsamlanmış stilleri, tekilleştirilmiş. Sayfaya bir kez basın. |
| `meta` | `SsrMetaSchema` | Evet | Sorulan kod, katalogda bulunup bulunmadığı ve render zamanı. |

**Yanıt: 304 Değişmedi** — `If-None-Match` matched the digest (`v`).

**Hatalar:** 400 geçersiz istek, 401 anahtar gönderilmedi, 402 modül sözleşmede yok, 403 anahtar geçersiz ya da kapsamı yetmiyor, 404 kanal bilinmiyor, 429 hız sınırı. Gövde biçimi: [Hatalar ve Limitler](/docs/api-reference/errors-and-limits).

```http
GET /api/v1/public/sites/SITE_KEY/ssr/enrichments?code=ABC-1&slots=urun-aciklama-alti
x-selwise-api-key: swpk_live_...
```

## Kimlik doğrulama

Diğer public uçlar güvenliği doğrulanmış alan adından alır. Sunucudan gelen bir isteğin `Origin` başlığı yoktur ve bir tarayıcı da bu ucu kimliksiz çağıramasın diye, bu uç **her istekte, başlığın varlığından bağımsız olarak** **site API anahtarı** ister.

Anahtarı **Channels** ekranında kanalın satırındaki **⋮** menüsünden **API keys**'i açıp **Purpose** (Kullanım amacı) olarak **Server-side rendering** (Sunucu taraflı render) seçerek oluşturun. Anahtar `swpk_live_` ile başlar ve yalnızca bir kez gösterilir. Ayrıntı: [API Anahtarları](/docs/channels/api-keys).

`ssr_read` kapsamı mobil SDK'nın `mobile_read`/`mobile_write` çiftiyle aynı torbada değildir: SSR anahtarı sizin uygulama sunucunuzda yaşar, dağıtım hattınızdan, CI günlüklerinizden ve imajlarınızdan geçer; sızan bir SSR anahtarı hiçbir şey yazamaz ve iptali mobil uygulamayı durdurmaz. `ssr_read` bu uç dışında hiçbir ucu açmaz.

SSR yalnızca **web kanalı** içindir: bir uygulama kanalının sayfa şablonu yoktur. Uygulama kanalında bir `ssr_read` anahtarı `403` alır; uygulamanın karşılığı [`app/enrichments`](/docs/api-reference/enrichment-api#appenrichments) ucudur.

| Yanıt | Anlamı |
| --- | --- |
| `401` | `x-selwise-api-key` başlığı yok. Bir şey sunmadınız |
| `403` | Anahtar geçersiz, iptal edilmiş ya da `ssr_read` kapsamı yok; ya da kanal bir web kanalı değil; ya da hesabın teslimatı kapalı |
| `404` | `siteKey` bulunamadı |
| `429` | Anahtar başına hız sınırı; `Retry-After` saniyedir |

## Hız sınırı

Anahtar başına, IP başına değil: varsayılan **6000 istek/dakika**. Sınır, anahtar doğrulanmadan **önce** sayılır; yanlış anahtarla yapılan deneme de sayaca girer (anahtar deneme saldırısının veritabanına ulaşmasını sınırlamak için).

Sebebi doğrudan sizinle ilgili: diğer public uçları alışverişçilerin tarayıcıları çağırır, yani bir IP kabaca bir ziyaretçidir. SSR çağrısını sizin sunucularınız yapar; bütün mağaza trafiği birkaç adresten çıkar. IP başına sayılsaydı yoğun bir mağaza paylaşılan public kovayı saniyeler içinde tüketir ve **widget'ınızı, olay akışınızı ve aramanızı da** durdururdu.

Yanıt `X-RateLimit-Limit` ve `X-RateLimit-Remaining` başlıklarını taşır. Selwise'in kendi sınırlayıcı deposu erişilemezse sınır **açık bırakılır** (istek reddedilmez): ürün sayfanız bizim sınırlayıcımızın arızası yüzünden render olmamalıdır.

## İstek

Bir blok birden çok dilde yazılmışsa `lang` hangisinin basılacağını seçer; her dil ayrı önbelleğe yazılır. Hazır Akinon yardımcıları dili kendisi gönderir: PZ Classic Django'nun etkin dilini okur, PZ Next yardımcısının son parametresi dildir.

İki yazım da kabul edilir, çünkü ikisi de doğal olarak yazılan biçimdir: Django'nun `urlencode` çıktısı tekrarlı, elle kurulan bir Next.js adresi virgüllü olur.

Yuva adları kural gibi katlanır: küçük harf, boşluk ve alt çizgi tire olur (`Urun_Aciklama` → `urun-aciklama`); katlandıktan sonra geçerli olmayan ad (64 karakter, harf-rakam-tire, uçlarda tire yok) atılır. Şablonunuza panelin **Saved as** (Kaydedilecek hâli) satırındaki adı yazın.

**Hata yerine boş yanıt.** `code` boş, 100 karakterden uzun ya da `slots` boşsa uç `400` **vermez**: her istenen yuvayı boş bırakan bir `200` ve `Cache-Control: no-store` döner. Çağıran şablon, kendi sayfa modelinde ne varsa onu yeni okumuştur; bir veri sorunu yüzünden ürün sayfasının `400` ile kırılması istenmez, çünkü render hata durumunda sessizce boş kalacak şekilde tasarlanmıştır.

## Yanıt

```json
{
  "v": "m3k1p.4",
  "mode": "anonymous",
  "slots": {
    "urun-aciklama-alti": {
      "html": "<div data-selwise-root=\"true\" data-selwise-entity-type=\"enrichment\" data-selwise-entity-id=\"8f3a2b7c-...\" data-selwise-ssr=\"1\" data-selwise-ssr-v=\"m3k1p.4\" data-selwise-ssr-slot=\"urun-aciklama-alti\"><div data-selwise-enrichment=\"5d1e9c0a...\">...</div></div>",
      "assignmentIds": ["8f3a2b7c-..."]
    },
    "sepet-ustu": { "html": "", "assignmentIds": [] }
  },
  "css": "[data-selwise-enrichment=\"5d1e9c0a...\"] .spec-table{...}",
  "meta": {
    "code": "ABC-1",
    "resolved": true,
    "renderedAt": "2026-10-09T09:12:00.000Z"
  }
}
```

Alanların tablosu yukarıdaki uç kutusundadır. Bir şablon yazarken bilmeniz gerekenler:

- İstenen **her** yuva (katlanmış adıyla) `slots` içinde bulunur; içeriği olmayan yuva boş dize ve boş dizi taşır. Anahtar hiç eksilmez, şablon null denetimi yazmaz.
- Bir yuvada birden çok kural varsa HTML'leri sırayla art arda gelir; `assignmentIds` çizim sırasıdır.
- `meta.resolved` `false` ise içerik yine gelir; yalnızca ürün yer tutucuları (`${title}`, `${price}` ...) boş kalır.
- `meta.renderedAt` yanıtın üretildiği andır; önbellekten dönen yanıtta eski bir an olabilir.

`css` alanının ayrı olması zorunlu: zenginleştirme izin listesi `style` etiketini yasaklar, yani markup'ın içine gömülü CSS zaten silinir. Stil blok kapsamına alınmış olduğu için `head` erişiminiz yoksa içeriğin yanına da basabilirsiniz.

### Kök öğenin imzası

Her kuralın HTML'i tek bir kök öğeyle sarılır ve widget onu şu niteliklerden tanır; bunları değiştirmeyin ve silmeyin:

| Nitelik | Anlamı |
| --- | --- |
| `data-selwise-root="true"` | Widget'ın kendi çizdiği her öğede bulunan kök işareti |
| `data-selwise-entity-type="enrichment"` | Varlığın türü |
| `data-selwise-entity-id` | Kuralın kimliği: widget bu kuralı **bir daha çizmez** |
| `data-selwise-ssr="1"` | Bu kökü sunucunun yazdığını bildirir; widget çizmek yerine ölçümü devralır |
| `data-selwise-ssr-v` | Sunucunun yanıt aldığı sürüm damgası |
| `data-selwise-ssr-slot` | Köklerin hangi yuvaya yazıldığı |

İçeride her blok bir `data-selwise-enrichment="<blokKimliği>"` sarmalayıcısındadır; `css` alanındaki kurallar bu niteliğe göre kapsamlanmıştır.

## Hangi kurallar döner

Yalnızca sunucuda basılabilecek ve **ziyaretçiye göre değişmeyen** kurallar:

| Döner | Dönmez | Neden |
| --- | --- | --- |
| **Nerede oluşturulur** alanı **Sunucu, tarayıcı yedekli** (`auto`) ya da **Yalnızca sunucu** (`server`) olan, yuva adı dolu, etkin (başlangıç ve bitiş penceresinde) kurallar | Test modundaki kurallar | Önbelleğe donan test içeriği herkese gider ve mağazadaki test bayrağı hiç sorulmaz |
| Tüm ziyaretçileri hedefleyen kurallar (segmentsiz, ya da segment listesi boş) | Segment hedefli kurallar | Kişiye göre değişen bir cevaptır; paylaşılan bir önbellekte bir ziyaretçinin segmenti herkese yapıştırılır |
| Ürüne kod listesiyle ya da katalog koşuluyla eşleşen kurallar | **Yalnızca tarayıcı** (`client`) kuralları, yuva adı olmayan kurallar | Adresi yoktur; yalnızca tarayıcıda yaşar |

Aynı yuvaya birden çok kural düşerse sıra: **Priority** (Öncelik) yüksek olan önce, eşitlikte eski olan önce. **When rules collide** (Kurallar çakıştığında) ayarı **Render only the highest-priority block** olan bir kural yuvayı tek başına alır. Bu sıra, widget'ın tarayıcıda kullandığıyla aynıdır; sunucuda ve tarayıcıda aynı içerik görünür.

## Önbellekleme

İki ayrı katman var ve ikisi de anlamlı:

**Selwise tarafında.** Yanıt (ürün kodu, yuva kümesi ve sürüm damgası anahtarlı) **5 dakika** tutulur; içeriği olmayan yanıt 1 dakika, çünkü bir ürünün kural listesine yeni eklenmesi çabuk görünmelidir. Sürüm damgası anahtarın parçası olduğu için bir kuralı panelden düzenlediğinizde yeni istekler yeni içeriği alır.

**Sizin tarafınızda.** Yanıt şu başlıklarla gelir:

```http
Cache-Control: public, max-age=60, stale-while-revalidate=600
ETag: W/"..."
Vary: x-selwise-api-key
```

`ETag`'i saklarsanız tekrar render bir gövde yerine `304` maliyetine iner (`If-None-Match` gönderin). ETag **içerikten** (sürüm, yuvalar, css) türetilir, `renderedAt`'ten değil: bayt bayt aynı içerik için yeni bir doğrulayıcı çıkmaz. `max-age` kasten kısa: bu HTML sizin sayfanızın içine girdiği anda bizim önbelleğimizin ulaşamayacağı bir yere geçer ve panelde yapılan bir düzenleme oraya yetişemez. `Vary: x-selwise-api-key` paylaşılan bir önbelleğin bir merchant'ın içeriğini başkasının anahtarına sunmasını engeller.

Bir ürün başına bir upstream çağrısı yeter: sayfa önbelleğiniz ya da süreç içi önbellek 60 saniye tutarsa yoğun bir ürün sayfası dakikada bir Selwise çağrısına iner.

## Zaman aşımı ve yedek davranışı

Çağrıyı **kısa bir zaman aşımıyla** yapın (önerilen 400 ms; sağlıklı bir çağrının kat kat üstü, alışverişçinin fark etmeyeceği kadar kısa) ve hatayı yutun. Ürün sayfanız bizim yüzümüzden beklememeli.

Bozulma merdiveni şu sırayla iner ve hiçbir basamak diğerini beklemez:

```text
SSR başarılı        → içerik ilk baytta, tarayıcı botu görür, widget ölçümü devralır
SSR başarısız       → şablon hiçbir şey basmaz → kural "Sunucu, tarayıcı yedekli" ise widget tarayıcıda basar
widget de yoksa     → sayfa mağazanın kendi hâliyle çıkar, hiçbir şey kırılmaz
```

Yani istemcinizi şöyle yazın: zaman aşımı, ağ hatası, `4xx`, `5xx` ve bozuk JSON **hepsi** boş dize döner. `404` ve `403` kalıcı hatalardır (yanlış anahtar, yanlış `siteKey`, SSR'ı olmayan kanal); onları günlüğe **uyarı olarak** yazın ve alarm kurun, çünkü bu durumda içerik hiç gelmez ve sayfa yine de başarılı görünür.

## Boyut sınırı

Bir yanıtın markup'ı en çok 256 KB taşır. Sınıra ulaşıldığında yanıt **kural sınırında kesilir**: ilk kurallar gelir, sınırı aşan kural (parçası değil, tamamı) atlanır; sunucu günlüğüne bir uyarı düşer. Bu HTML sizin sayfanızın içinde her ürün görüntülemesinde taşındığı için sınır bir formalite değildir.

## Hatalar

| Durum | Ne zaman |
| --- | --- |
| `200` | Her durumda içerik yoksa bile; `slots.*.html` boş olabilir |
| `304` | `If-None-Match` ETag ile eşleşti, gövde yok |
| `401` | Başlık yok |
| `403` | Anahtar geçersiz/iptal/kapsam yok, web kanalı değil, hesap teslimatı kapalı |
| `404` | `siteKey` yok |
| `429` | Anahtar başına dakikada 6000 aşıldı |
| `5xx` | Selwise tarafında geçici hata; boş dize basın, bir sonraki ziyarette yeniden deneyin |

Hiçbir `400` yoktur: geçersiz parametreler boş yanıt verir.

## Arama ve öneriler

Arama ve öneri uçları da sunucudan çağrılabilir; ikisi de JSON döndürür ve markup üretmez: liste şablonu sizin tasarımınızdır, bizim basacağımız markup orada yabancı durur. Bkz. [Arama API'si](/docs/api-reference/search-api), [Öneri API'si](/docs/api-reference/recommendations-api). Öneri şeritlerinin sunucuda HTML olarak oluşturulması bugün yoktur; kampanya ve widget'lar da sunucuda basılmaz (geri sayım, sepet ve ziyaretçi sayısı gibi içerikler zamana ve ziyaretçiye göre değişir ve bir sayfa önbelleği tek bir cevabı herkese donduracağından bilerek dışarıda).
