# Ürün Uçları

https://dev.wisecp.com/tr/urun-uclari

Ürün kataloğunu listeleyen, ürün açan, okuyan, güncelleyen ve silen dokuz uç.

## Genel Bakış

Bu uçlar katalogdaki ürünün kendisini yönetir: listeleme, oluşturma, okuma, güncelleme ve silme. Yanlarındaki üç arama ucu oluşturma sırasında hangi değerlerin geçerli olduğunu söyler.

Oluşturma ile güncelleme arasında bilinçli bir iş bölümü var: oluşturma yalnız **iskeleti** kurar, ürünü satılabilir yapan her şey güncelleme ucundan girer. Panel de aynı sırayı izler.

## Referans

### Ürünleri Listeleme

get/api/v1/admin/products

`Products/GetProducts` admin sayfalı

Ürün kataloğunu döndürür. Süzgeç vermezseniz tüm tipler gelir, en yeniden eskiye.

Sorgu parametreleri 7

searchstringÜrün başlığında arar.

typestringTipe göre süzer: `hosting`, `server`, `software`, `ssl` ya da `special`.

group_idintÖzel grup kimliği. Kullanmak `type` alanını `special` yapmayı gerektirir.

categoryintKategori kimliğine göre süzer.

statusstring`active` ya da `inactive`.

pageintVarsayılan 1.

limitintVarsayılan 25, en çok 100.

Dönen alanlar data[] — 8

idintÜrün kimliği.

titlestringÜrün başlığı, geçerli dilde.

typestringÜrünün davranış tipi.

modulestringBağlı modül. Bağlı değilse `none`.

statusstring`active` ya da `inactive`.

categoryobjectKategori: `{id, title}`. Kategorisiz üründe başlık boş gelir.

service_countintBu üründen açılmış hizmet sayısı.

created_atstringOluşturma zamanı.

Meta 4

totalintSüzgece uyan toplam kayıt.

pageintBulunduğunuz sayfa.

limitintSayfa boyutu.

next_pageintSonraki sayfa. `0` son sayfada olduğunuz anlamına gelir.

Hatalar 1

insufficient_scope403Anahtar gerekli kapsamı taşımıyor.

İstek cURL JavaScript PHP (HTTP) PHP (Dahili)

```bash
curl -G 'https://panel.ornek.com/api/v1/admin/products' \
  -H "Authorization: Bearer $API_KEY" \
  -d type=hosting \
  -d limit=50
```

```javascript
const url = new URL('https://panel.ornek.com/api/v1/admin/products');
url.searchParams.set('type', 'hosting');
url.searchParams.set('limit', '50');

const res  = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();
```

```php
$url = 'https://panel.ornek.com/api/v1/admin/products?' . http_build_query(['type' => 'hosting', 'limit' => 50]);

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
$page = 1;
$all  = [];

do {
    $response = Api::Products()->GetProducts([], [
        'type'  => 'hosting',
        'page'  => $page,
        'limit' => 100,
    ]);

    $all  = array_merge($all, $response['data']);
    $page = $response['meta']['next_page'];
} while ($page > 0);
```

Yanıt 200

```json
{
  "data": [
    {
      "id": 15,
      "title": "Starter SSD 1",
      "type": "hosting",
      "module": "cPanel",
      "status": "active",
      "category": { "id": 439, "title": "Economy Web Hosting" },
      "service_count": 12,
      "created_at": "2026-01-01 10:00:00"
    }
  ],
  "meta": { "total": 499, "page": 1, "limit": 25, "next_page": 2 }
}
```

### Ürün Oluşturma

post/api/v1/admin/products

`Products/CreateProduct` admin 201

İskelet bir ürün açar. Fiyat, limit ve dil içeriği sonradan güncelleme ucuyla girilir.

Gövde 6

typestringzorunluÜrün tipi: `hosting`, `server`, `software` ya da `special`. `ssl` tek başına açılamaz; özel grup içinde yaşar.

namestringzorunluÜrün başlığı. Tüm dillere aynı metin yazılır.

group_idintÖzel grup kimliği. Tip `special` ise zorunlu.

categoryintKategori kimliği.

modulestringBağlanacak modülün adı. Verirseniz otomatik kurulum açılır.

hiddenboolÜrünü vitrinde gizler.

Dönen alanlar data

dataobjectOluşturulan ürün, `201` ile döner. Detay ucuyla aynı şekildedir.

Hatalar 5

invalid_type422Tip izinli değerlerden değil.

name_required422`name` boş.

group_required422Tip `special` ama grup verilmedi.

blocked_by_gate422`gate:product.create` kancası işlemi veto etti.

create_failed422Oluşturma reddedildi. Grup yok ya da adres çakışıyor olabilir.

İstek cURL JavaScript PHP (HTTP) PHP (Dahili)

```bash
curl -X POST 'https://panel.ornek.com/api/v1/admin/products' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"type":"hosting","name":"Starter SSD 1","category":439,"module":"cPanel"}'
```

```javascript
const res = await fetch('https://panel.ornek.com/api/v1/admin/products', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    type: 'hosting',
    name: 'Starter SSD 1',
    category: 439,
    module: 'cPanel',
  }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.ornek.com/api/v1/admin/products');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'type'     => 'hosting',
        'name'     => 'Starter SSD 1',
        'category' => 439,
        'module'   => 'cPanel',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// Olusturma iskelet birakir; kullanilabilir bir urun icin ardindan guncelleyin.
$created = Api::Products()->CreateProduct([
    'type'     => 'hosting',
    'name'     => 'Starter SSD 1',
    'category' => 439,
    'module'   => 'cPanel',
]);

Api::Products()->UpdateProduct([
    'id'     => $created['data']['id'],
    'limits' => ['disk' => 100, 'bandwidth' => 'unlimited'],
]);
```

### Ürün Detayı

get/api/v1/admin/products/{id}

`Products/GetProduct` admin modüle göre değişir

Ürünün tüm ayarlarını, ilişkilerini ve her dildeki içeriğini döndürür.

Dönen alanlar data — 26

idintÜrün kimliği.

typestringÜrünün davranış tipi.

group_typestringGrup tipi anahtarı.

group_idintÖzel grup kimliği. Gruplu değilse 0.

categoryobjectKategori: `{id, name}`.

statusstring`active` ya da `inactive`.

visibilitystring`visible` ya da `invisible`.

modulestringBağlı modülün adı.

module_dataobjectModülün kendi yapılandırması. Alanlar tamamen modüle bağlıdır; sabit bir şema yoktur.

optionsobjectÜrün seçenekleri. Alanlar tipe ve modüle göre değişir.

additional_taxobjectEk vergi: `{enabled, items}`.

tax_exemptboolVergiden muaf mı.

override_user_currencyboolMüşterinin para birimini yok sayar mı.

upgrade_enabledboolYükseltme açık mı.

affiliate_disabledboolSatış ortaklığı kapalı mı.

affiliate_ratefloatSatış ortaklığı oranı.

stockint | nullKalan stok. `null` sınırsız demektir.

rankintGörüntülenme sırası.

upgradeable_product_idsint[]Bu üründen geçilebilecek ürünler.

addon_idsint[]Bağlı add-on kimlikleri.

requirement_idsint[]Bağlı gereksinim kimlikleri.

notesstringYönetici notları. Müşteriye gösterilmez.

prorateobjectGün bazlı orantılama: `{enabled, days}`.

recurring_cycles_limitobjectYenileme sayısı sınırı: `{enabled, value}`.

auto_terminateobjectOtomatik sonlandırma: `{enabled, days}`.

langsobject 8 alanDil kodundan içerik nesnesine eşleme.

titlestringÜrün başlığı.

descriptionstringKısa açıklama.

contentstringUzun içerik.

routestringAdres parçası.

featuresstringÖzellik listesi.

seo_titlestringArama başlığı.

seo_keywordsstringArama anahtar kelimeleri.

seo_descriptionstringArama açıklaması.

Hatalar 2

not_found404Ürün bulunamadı.

insufficient_scope403Anahtar gerekli kapsamı taşımıyor.

İstek cURL JavaScript PHP (HTTP) PHP (Dahili)

```bash
curl 'https://panel.ornek.com/api/v1/admin/products/15' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch('https://panel.ornek.com/api/v1/admin/products/15', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
```

```php
$ch = curl_init('https://panel.ornek.com/api/v1/admin/products/15');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
$response = Api::Products()->GetProduct(['id' => 15]);

// module_data'nin sekli modulden geliyor — anahtar varligini kontrol edin.
$plan = $response['data']['module_data']['plan'] ?? null;
```

### Ürünü Güncelleme

patch/api/v1/admin/products/{id}

`Products/UpdateProduct` admin kısmi güvenli

Gönderdiğiniz alanları uygular, geri kalanını olduğu gibi bırakır.

Gövde 22

statusstring`active` ya da `inactive`.

categoryintKategori kimliği.

hiddenboolVitrinde gizler.

notesstringYönetici notları.

rankintGörüntülenme sırası.

stockint | nullStok. Boş bırakmak sınırsız demektir.

subdomainsstringAlt barındırmaya izinli ürünler.

affiliate_disabledboolSatış ortaklığını kapatır.

affiliate_ratefloatSatış ortaklığı oranı.

override_user_currencyboolMüşterinin para birimini yok sayar.

tax_exemptboolVergiden muaf kılar.

additional_taxobjectEk vergi yapılandırması.

allow_qtyboolSiparişte adet seçilmesine izin verir.

prorateobject`{enabled, days}`.

recurring_cycles_limitobject`{enabled, value}`.

auto_terminateobject`{enabled, days}`.

addon_idsint[]Bağlanacak add-on kimlikleri.

requirement_idsint[]Bağlanacak gereksinim kimlikleri.

upgradeable_product_idsint[]Yükseltme hedefi ürünler.

optionsobjectÜrün seçenekleri: `popular`, `auto_approval`, `auto_install`, `seo_index`, `restrict_access`, `order_limit_per_user`, `free_domain`, `domain_requirement`, `hide_domain`, `show_domain`, `hide_hosting`, `show_hosting`, `change_domain`, `renewal_selection_hide`, `download_link`, `demo_link`, `demo_admin_link`, `demo_admin_link`, `product_file_access`, `ctoc_service_transfer`, `server_group_id`, `server_id`, `activation_notification`. `domain_requirement`, hosting, server ve special ürünlerde `none`, `optional` ya da `required` değerini alır ve `hide_domain` / `show_domain` bayrağını türetir; yalnız eski bayrağı göndermek de çalışır, bilinmeyen değer yoksayılır.

limitsobjectBarındırma kaynak sınırları: `disk`, `bandwidth`, `email`, `database`, `addons`, `subdomain`, `ftp`, `park`, `max_email_per_hour`. Her biri sayı ya da `unlimited`. Boş değer, `null` ya da negatif sayı `unlimited` olarak kaydedilir; üründen okunan değer olduğu gibi geri gönderilebilir. Sayım sınırlarında `0` hak vermez.

langsobject 8 alanDil kodundan içerik nesnesine eşleme. Yalnız gönderdiğiniz diller değişir.

titlestringÜrün başlığı.

descriptionstringKısa açıklama.

contentstringUzun içerik.

routestringAdres parçası.

featuresstringÖzellik listesi.

seo_titlestringArama başlığı.

seo_keywordsstringArama anahtar kelimeleri.

seo_descriptionstringArama açıklaması.

Dönen alanlar data

dataobjectGüncel ürün. Detay ucuyla aynı şekildedir.

Hatalar 3

not_found404Ürün bulunamadı.

insufficient_scope403Anahtar gerekli kapsamı taşımıyor.

invalid_limit422Bir `limits` değeri sayı, `unlimited`, boş ya da `null` değil. `details.field` hangisi olduğunu söyler.

İstek cURL JavaScript PHP (HTTP) PHP (Dahili)

```bash
curl -X PATCH 'https://panel.ornek.com/api/v1/admin/products/15' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"status":"inactive","limits":{"disk":100,"bandwidth":"unlimited"}}'
```

```javascript
const res = await fetch('https://panel.ornek.com/api/v1/admin/products/15', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    status: 'inactive',
    limits: { disk: 100, bandwidth: 'unlimited' },
  }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.ornek.com/api/v1/admin/products/15');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'status' => 'inactive',
        'limits' => ['disk' => 100, 'bandwidth' => 'unlimited'],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// Yalniz Ingilizce metni degistirmek diger dillere dokunmaz.
$response = Api::Products()->UpdateProduct([
    'id'    => 15,
    'langs' => [
        'en' => ['title' => 'Starter SSD 1', 'features' => 'NVMe storage'],
    ],
]);
```

### Toplu Durum Değiştirme

post/api/v1/admin/products/bulk

`Products/BulkProducts` admin yalnız durum

Birden çok ürünün durumunu tek çağrıda değiştirir. Silme bu uçtan yapılamaz.

Gövde 2

idsint[]zorunluÜrün kimlikleri. En az bir tane.

actionstringzorunlu`active` ya da `inactive`.

Dönen alanlar data — 2

updatedint[]Durumu değişen kimlikler.

actionstringUygulanan durum.

Hatalar 2

ids_required422`ids` boş.

invalid_action422İşlem iki değerden biri değil.

İstek cURL JavaScript PHP (HTTP) PHP (Dahili)

```bash
curl -X POST 'https://panel.ornek.com/api/v1/admin/products/bulk' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"ids":[15,16],"action":"inactive"}'
```

```javascript
const res = await fetch('https://panel.ornek.com/api/v1/admin/products/bulk', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ ids: [15, 16], action: 'inactive' }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.ornek.com/api/v1/admin/products/bulk');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'ids'    => [15, 16],
        'action' => 'inactive',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
$response = Api::Products()->BulkProducts([
    'ids'    => [15, 16],
    'action' => 'inactive',
]);
```

### Ürünü Silme

delete/api/v1/admin/products/{id}

`Products/DeleteProduct` admin geri alınamaz

Ürünü siler. Dil kayıtları ve fiyatları birlikte gider.

Dönen alanlar data — 2

deletedboolSilme başarılı mı.

idintSilinen ürünün kimliği.

Hatalar 2

not_found404Ürün bulunamadı.

blocked_by_gate422`gate:product.delete` kancası işlemi veto etti.

İstek cURL JavaScript PHP (HTTP) PHP (Dahili)

```bash
curl -X DELETE 'https://panel.ornek.com/api/v1/admin/products/2030' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res = await fetch('https://panel.ornek.com/api/v1/admin/products/2030', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.ornek.com/api/v1/admin/products/2030');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
$response = Api::Products()->DeleteProduct(['id' => 2030]);
```

### Ürün Tiplerini Listeleme

get/api/v1/admin/products/types

`Products/GetProductTypes` admin arama ucu

Bu kurulumda kullanılabilen ürün tiplerini döndürür. Oluştururken geçerli değerler buradan gelir.

Dönen alanlar data[] — 4

keystringTip anahtarı. Oluşturma gövdesine bu değer yazılır.

titlestringGörüntülenecek etiket.

descriptionstringTipin açıklaması.

iconstringİkon sınıfı.

Hatalar 1

insufficient_scope403Anahtar gerekli kapsamı taşımıyor.

İstek cURL JavaScript PHP (HTTP) PHP (Dahili)

```bash
curl 'https://panel.ornek.com/api/v1/admin/products/types' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch('https://panel.ornek.com/api/v1/admin/products/types', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
```

```php
$ch = curl_init('https://panel.ornek.com/api/v1/admin/products/types');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
$response = Api::Products()->GetProductTypes();
```

### Grupları Listeleme

get/api/v1/admin/products/groups

`Products/GetProductGroups` admin arama ucu

Sabit ve özel katalog gruplarını döndürür. Her satır oluşturma için hazır bir tip ve grup çifti verir.

Dönen alanlar data[] — 4

keystringGrup anahtarı. Özel gruplarda kimlik son ekiyle gelir.

titlestringGörüntülenecek etiket.

typestringBu gruba karşılık gelen ürün tipi.

group_idintÖzel grup kimliği. Sabit gruplarda 0.

Hatalar 1

insufficient_scope403Anahtar gerekli kapsamı taşımıyor.

İstek cURL JavaScript PHP (HTTP) PHP (Dahili)

```bash
curl 'https://panel.ornek.com/api/v1/admin/products/groups' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch('https://panel.ornek.com/api/v1/admin/products/groups', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
```

```php
$ch = curl_init('https://panel.ornek.com/api/v1/admin/products/groups');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// Grup satiri olusturma govdesinin iki alanini birden verir.
$group = Api::Products()->GetProductGroups()['data'][0];

Api::Products()->CreateProduct([
    'type'     => $group['type'],
    'group_id' => $group['group_id'],
    'name'     => 'New product',
]);
```

### Kategorileri Listeleme

get/api/v1/admin/products/categories

`Products/GetProductCategories` admin arama ucu

Kategori listesini düz olarak döndürür. Ağacı üst kimliklerden siz kurarsınız.

Sorgu parametreleri 2

typestringGrup tipi. Varsayılan `hosting`.

group_idintÖzel grup kimliği. Sıfırdan büyük vermek tipi `special` yapar.

Dönen alanlar data[] — 4

idintKategori kimliği.

parent_idintÜst kategorinin kimliği. 0 üst seviye demektir.

titlestringKategori başlığı.

routestringAdres parçası.

Hatalar 1

insufficient_scope403Anahtar gerekli kapsamı taşımıyor.

İstek cURL JavaScript PHP (HTTP) PHP (Dahili)

```bash
curl -G 'https://panel.ornek.com/api/v1/admin/products/categories' \
  -H "Authorization: Bearer $API_KEY" \
  -d type=hosting
```

```javascript
const url = new URL('https://panel.ornek.com/api/v1/admin/products/categories');
url.searchParams.set('type', 'hosting');

const res  = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();
```

```php
$url = 'https://panel.ornek.com/api/v1/admin/products/categories?' . http_build_query(['type' => 'hosting']);

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
$rows = Api::Products()->GetProductCategories([], ['type' => 'hosting'])['data'];

$byParent = [];
foreach ($rows as $row) $byParent[$row['parent_id']][] = $row;
```

## Tuzaklar

> **Oluşturma satılabilir bir ürün vermez**
> 
> Yeni ürün iskelet hâlde doğar: varsayılan para biriminde sıfır tutarlı aylık bir fiyat konur ve başlık tüm dillere kopyalanır. Limitler, gerçek fiyat ve içerik girilene kadar ürün satışa hazır değildir. Oluşturmayı bir güncelleme çağrısı takip etmelidir.

> **Güncelleme bazı bölümleri dışarıda bırakır**
> 
> Dönemsel fiyatlandırma, metrik fiyatlandırma, modül yapılandırması ve lisans parametreleri bu uçtan **düzenlenmez**. Bunlar mevcut değerleriyle yeniden yazılır, yani gövdenizde olmamaları onları silmez. Kendi uçlarında yönetilirler.

> **İki alanın şeması modüle bağlıdır**
> 
> Detayda dönen `module_data` ve `options` alanlarının içeriği bağlı modüle ve ürün tipine göre değişir; sabit bir şemaları yoktur. Bunlara doğrudan erişen kod modül değişince kırılır, anahtar varlığını kontrol edin.

> **Toplu uç silmez**
> 
> Toplu işlem yalnız durum değiştirir. Ürün silmek tek tek yapılır ve her silme `gate:product.delete` kancasından geçer; bir eklenti veto edebilir.

## İlgili Makaleler

- [Ürün Kategorileri](https://dev.wisecp.com/tr/urun-kategorileri)
- [Add-on Tanımları](https://dev.wisecp.com/tr/addon-tanimlari)
- [Ürün Medyası](https://dev.wisecp.com/tr/urun-medyasi)
