============================================================================== RABISU BAYI (RESELLER) API — TAM DOKUMANTASYON ============================================================================== Bu dosya Rabisu Bayi API dokumantasyonunun tamamini tek bir duz metin dosyasinda toplar. Bir yapay zeka asistanina (ChatGPT, Claude vb.) yukleyip entegrasyon sorularinizi sorabilirsiniz. Web surumu: https://www.rabisu.com/tr/docs/bayi-api/giris API surumu: 1.0.0 Uretim tarihi: 2026-10-06 Production temel URL: https://api.rabisu.com/api/v1 ICINDEKILER BOLUM 1 — REHBERLER - Rabisu Bayi API — Genel Bakış - Kimlik Doğrulama - Idempotency (Tekrar Güvenli İstekler) - Hız Sınırlama (Rate Limit) - Hata Kodları - Sipariş Yaşam Döngüsü - Webhooks BOLUM 2 — API REFERANSI (ENDPOINT'LER) — 36 endpoint [System] GET /health — Saglik kontrolu (auth gerektirmez) GET /openapi.yaml — OpenAPI 3.0 spec'inin ham YAML icerigi [Auth] GET /auth/me — Authenticated key meta bilgisini doner GET /auth/balance — Bayi WHMCS kredi bakiyesi (APCu 60s cache) GET /auth/balance/movements — Bayi bakiye hareketleri (canli WHMCS tblcredit) [Products] GET /products — Urun katalogu listesi (+ stok aktiflik flag'i) GET /products/{id} — Tek urun detayi [Orders] GET /orders — Bayinin siparis listesi (provision kayitlari) POST /orders — Yeni siparis olustur + bakiyeden OTOMATIK odeme dene GET /orders/{id} — Tek siparis detayi (bayi izolasyonu zorunlu) POST /orders/{id}/settle — Bekleyen odemeyi krediyle tahsil et (InvoicePaid) [Services] GET /services — Bayinin hizmetleri — toplu liste (imlec veya sayfa + arama) GET /services/{id} — VPS hizmet detayi (WHMCS + Virtualizor birlesimi) DELETE /services/{id} — Servisi WHMCS modul hook'u ile terminate et (asenkron) GET /services/{id}/status — VPS anlik metrikleri (CPU/RAM/disk/net) GET /services/{id}/credentials — Erisim bilgileri (username/password/ip/hostname/panel_url) GET /services/{id}/os-templates — Bu VPS icin rebuild'e izinli OS sablonlari (servis-spesifik filtre) POST /services/{id}/power/{action} — VPS power islemi (start/stop/restart/poweroff) POST /services/{id}/rebuild — VPS OS reinstall (asenkron, 202 Accepted) POST /services/{id}/vnc-token — Kisa omurlu (60s) NoVNC baglanti bilgisi POST /services/{id}/password — VPS root parolasini degistir POST /services/{id}/suspend — Servisi WHMCS modul hook'u ile suspend et POST /services/{id}/unsuspend — Servisin suspend'ini WHMCS modul hook'u ile geri al POST /services/{id}/upgrade — Servisin config option'larini yukselt (upgrade) [Webhooks] GET /webhooks — Bayinin webhook kayitlarini listele POST /webhooks — Yeni webhook endpoint kaydet GET /webhooks/{id} — Tek webhook detayi PATCH /webhooks/{id} — Webhook kismi guncelle (reaktive / event_types / aciklama) DELETE /webhooks/{id} — Webhook sil (soft delete) [ModuleReleases] GET /module-releases/{module}/latest — Modulun en yeni gecerli surumu GET /module-releases/{module}/{version}/download — Imzali modul zip'ini indir [Admin] GET /admin/keys — Bir bayinin aktif API key'lerini listele (secret YOK) POST /admin/keys — Bayi icin yeni API key uret (secret TEK SEFER doner) POST /admin/keys/{id}/webhook-secret — Eski anahtara webhook secret ver (yoksa, tek sefer) POST /admin/keys/{id}/rotate — Bayi API key secret'ini yenile (secret TEK SEFER doner) POST /admin/keys/{id}/revoke — Bayi API key'ini iptal et (soft delete) BOLUM 3 — VERI SEMALARI (components/schemas) ============================================================================== BOLUM 1 — REHBERLER ============================================================================== # Rabisu Bayi API — Genel Bakış Rabisu Bayi API, bayilerin kendi WHMCS kurulumları üzerinden Rabisu Virtualizor VPS ürünlerini satabilmesi için tasarlanmış bir REST API'dir. Sipariş oluşturma, servis yaşam döngüsü yönetimi (askıya alma, iptal, yeniden kurulum), kaynak yükseltme, kredi bakiyesi sorgulama ve webhook bildirimleri sunar. :::tip[Tüm dokümantasyon tek dosyada (AI için)] Sayfaları tek tek gezmek yerine API dokümantasyonunun tamamını (rehberler, tüm endpoint'ler, parametreler, yanıtlar, örnekler ve veri şemaları) tek bir metin dosyası olarak indirebilirsiniz. Dosyayı ChatGPT, Claude gibi bir yapay zeka asistanına yükleyip entegrasyon sorularınızı doğrudan sorabilirsiniz. 📄 Türkçe dokümanı indir (.txt) · English version (.txt) ::: ## Temel Bilgiler | Özellik | Değer | |---|---| | **Production temel URL** | `https://api.rabisu.com/api/v1` | | **Protokol** | HTTPS zorunlu (TLS) | | **Kimlik doğrulama** | HTTP Basic Auth (API Key + Secret) | | **İçerik tipi** | `application/json` | | **Sürüm** | Tüm yollar `/api/v1` altında | ## İstek Formatı Tüm istek gövdeleri JSON olmalıdır. Kimlik doğrulama her istekte HTTP Basic Auth başlığı ile gönderilir (bkz. Kimlik Doğrulama). ```bash curl -u "rsk_live_xxx:rsks_yyy" \ https://api.rabisu.com/api/v1/auth/me ``` ## Standart Yanıt Zarfı Tüm yanıtlar aynı zarf yapısını kullanır. Başarılı yanıt: ```json { "success": true, "data": { "reseller_id": 42, "name": "Örnek Bayi" }, "meta": { "trace_id": "1f9a...uuid", "timestamp": "2026-07-22T12:00:00Z" } } ``` Hata yanıtı: ```json { "success": false, "error": { "code": "insufficient_credit", "message": "Kredi bakiyesi yetersiz.", "details": { "required": "10.00", "available": "3.50" } }, "meta": { "trace_id": "1f9a...uuid", "timestamp": "2026-07-22T12:00:00Z" } } ``` - **`success`**: İşlemin sonucu (`true` / `false`). - **`data`**: Yalnızca başarılı yanıtlarda bulunur. - **`error`**: Yalnızca hatalı yanıtlarda bulunur — `code` (İngilizce slug), `message` (insan-okunur) ve opsiyonel `details`. - **`meta.trace_id`**: Her istek için benzersiz izleme kimliği. Destek talebi açarken bu değeri paylaşın. - **`meta.timestamp`**: ISO 8601 UTC zaman damgası. ## İzleme Kimliği (Trace ID) Her yanıtta `X-Request-ID` başlığı ve `meta.trace_id` alanı döner. İsteğinize kendi `X-Request-ID` başlığınızı eklerseniz sistem onu korur; eklemezseniz otomatik bir UUID üretilir. Sorun giderirken bu kimliği loglarınızda saklayın. ## Sonraki Adımlar - Kimlik Doğrulama — HTTP Basic Auth ile API Key/Secret kullanımı - Idempotency — Tekrar güvenli istekler - Hız Sınırlama (Rate Limit) — Kota başlıkları - Hata Kodları — Tüm hata slug'ları - Sipariş Yaşam Döngüsü — POST /orders ve provizyon akışı - Webhooks — Olay bildirimleri ve imza doğrulama ------------------------------------------------------------------------------ # Kimlik Doğrulama Rabisu Bayi API **HTTP Basic Authentication** kullanır. Her istekte API Key (kullanıcı adı) ve API Secret (şifre) gönderilir. JWT veya oturum (session) kullanılmaz — API tamamen durumsuzdur (stateless). ## Kimlik Bilgileri Bayi olduğunuzda size iki değer verilir: | Alan | Örnek | Rol | |---|---|---| | **API Key** | `rsk_live_ab55mtkb7azdr1pyh5xs56whcab4pjv9` | Kullanıcı adı | | **API Secret** | `rsks_zfe4yk848aa737k3s1q1h3mmnd963jk6651fcc3` | Şifre | > **Önemli:** API Secret **yalnızca bir kez** gösterilir. Güvenli bir yerde > saklayın. Kaybederseniz secret'ı yenilemeniz (rotate) veya yeni anahtar > oluşturmanız gerekir. ## İstek Örneği Basic Auth başlığı `Authorization: Basic base64(api_key:api_secret)` biçimindedir. Çoğu HTTP istemcisi bunu sizin için üretir: ```bash curl -u "rsk_live_ab55mtkb7azdr1pyh5xs56whcab4pjv9:rsks_zfe4yk848..." \ https://api.rabisu.com/api/v1/auth/me ``` PHP (cURL) örneği: ```php $ch = curl_init('https://api.rabisu.com/api/v1/auth/me'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_USERPWD, $apiKey . ':' . $apiSecret); $response = curl_exec($ch); ``` ## Başarısız Kimlik Doğrulama Kimlik bilgileri eksik veya hatalıysa **401 Unauthorized** döner. Güvenlik gereği tüm hata durumları aynı genel mesajı verir (hangi alanın yanlış olduğu sızdırılmaz): ```json { "success": false, "error": { "code": "unauthorized", "message": "Kimlik doğrulama başarısız." }, "meta": { "trace_id": "...", "timestamp": "..." } } ``` 401 yanıtlarında RFC 7235 uyarınca şu başlık da döner: ``` WWW-Authenticate: Basic realm="Rabisu Reseller API", charset="UTF-8" ``` ## Güvenlik Notları - API Secret'ı **asla** istemci tarafı (tarayıcı) kodunda kullanmayın. API yalnızca sunucudan-sunucuya (server-to-server) çağrılar için tasarlanmıştır. - `Authorization` başlığını **hiçbir loga** düz metin yazmayın. - Secret sızması durumunda anahtarı derhal yenileyin (rotate) veya iptal edin (revoke). - IP whitelist tanımlıysa yalnızca izin verilen IP'lerden erişebilirsiniz. ## Ana Uç Nokta: `GET /auth/me` Kimlik bilgilerinizi doğrulamak için en basit uç nokta budur. Anahtarın sahibi, izinleri ve `auto_apply_credit` bayrağını döner: ```json { "success": true, "data": { "reseller_id": 42, "name": "Örnek Bayi", "permissions": ["*"], "auto_apply_credit": true, "last_used_ip": "203.0.113.5" }, "meta": { "trace_id": "...", "timestamp": "..." } } ``` ------------------------------------------------------------------------------ # Idempotency (Tekrar Güvenli İstekler) Ağ hataları veya zaman aşımları nedeniyle bir isteği tekrar göndermeniz gerekebilir. Idempotency, aynı isteğin yanlışlıkla iki kez işlenmesini (örneğin çift sipariş, çift ücretlendirme) önler. ## Nasıl Çalışır? Mutasyon içeren isteklere (`POST`, `DELETE`) `Idempotency-Key` başlığı ekleyin. Bu başlık **UUID v4** biçiminde benzersiz bir değer olmalıdır: ```bash curl -X POST https://api.rabisu.com/api/v1/orders \ -u "rsk_live_xxx:rsks_yyy" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 9f1c2d3e-4b5a-6c7d-8e9f-0a1b2c3d4e5f" \ -d '{ "product_id": 101, "period": "monthly" }' ``` Davranış: | Durum | Sonuç | |---|---| | **Key yok** | Normal işlenir (idempotency korumasız). | | **Key var, aynı gövde** | 24 saat içindeki ilk yanıt cache'ten aynen döner — işlem tekrar yürütülmez. | | **Key var, farklı gövde** | `422 idempotency_key_reuse` hatası — aynı anahtar farklı içerikle kullanılamaz. | ## Zorunlu Olduğu Uç Noktalar Bazı finansal / yıkıcı uç noktalarda `Idempotency-Key` **zorunludur**; eksikse `400 missing_idempotency_key` döner: - `POST /orders` — sipariş oluşturma - `POST /services/{id}/upgrade` — kaynak yükseltme (fark faturası) - `POST /services/{id}/rebuild` — işletim sistemi yeniden kurulumu - `POST /services/{id}/power/{action}` — güç işlemleri - `DELETE /services/{id}` — servis iptali (disk silinir) ## Öneriler - Her mantıksal işlem için **kararlı (stable)** bir anahtar üretin. Örneğin "servis 500 için aylık yükseltme" isteğini tekrar denerken **aynı** anahtarı kullanın ki çift faturalama olmasın. - Yeni bir işlem için **yeni** anahtar üretin. - Anahtarlar 24 saat boyunca saklanır; bu süre sonunda aynı anahtar yeniden kullanılabilir. ------------------------------------------------------------------------------ # Hız Sınırlama (Rate Limit) API'yi aşırı yüklenmeye karşı korumak için istek sayısı dakika bazında sınırlanır. Sınır, kayan pencere (sliding window) yöntemiyle hesaplanır. Her API anahtarının limiti **ayrıdır** — ortak/global bir limit yoktur. Limit, uç nokta grubuna (kova) göre uygulanır; bir kovadaki tüketim diğerlerini etkilemez. ## Kotalar | Kova (bucket) | Varsayılan limit | Kapsam | |---|---|---| | `default` | 60 istek / dakika | Sipariş, ürün, bakiye, webhook, VNC, erişim bilgileri, yaşam döngüsü (suspend/unsuspend/terminate) | | `services_read` | 150 istek / dakika | `GET /services`, `GET /services/{id}` | | `services_status` | 300 istek / dakika | `GET /services/{id}/status` (yüksek frekanslı polling) | | `services_changes` | 120 istek / dakika | Değişiklik uç noktaları (power, rebuild, parola, upgrade) | Büyük bayiler için kova bazlı artışlar Rabisu tarafından tanımlanır. Anahtarınızın **etkin** limitlerini `GET /auth/me` yanıtındaki `rate_limits` alanından okuyabilirsiniz: ```json "rate_limits": { "default": 60, "services_read": 150, "services_status": 300, "services_changes": 120 } ``` ## Yanıt Başlıkları Her yanıtta güncel kota bilgisi döner: | Başlık | Anlamı | |---|---| | `X-RateLimit-Limit` | Dakikadaki toplam kota | | `X-RateLimit-Remaining` | Kalan istek sayısı | | `X-RateLimit-Reset` | Kotanın yenileneceği Unix zaman damgası | ## Kota Aşımı (429) Kota aşıldığında **429 Too Many Requests** döner ve `Retry-After` başlığı kaç saniye beklemeniz gerektiğini bildirir: ``` HTTP/1.1 429 Too Many Requests Retry-After: 42 X-RateLimit-Limit: 60 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1785000000 ``` ```json { "success": false, "error": { "code": "rate_limited", "message": "İstek limiti aşıldı. Lütfen daha sonra tekrar deneyin." }, "meta": { "trace_id": "...", "timestamp": "..." } } ``` ## Öneriler - `429` alırsanız `Retry-After` süresi kadar bekleyip **üstel geri çekilme (exponential backoff)** ile tekrar deneyin. - Servis durumu için sürekli polling yerine mümkünse Webhooks kullanın — bildirimler anlık gelir, kota tüketmez. - Toplu işlemlerde istekleri zamana yayın; aynı anda yüzlerce istek göndermeyin. ------------------------------------------------------------------------------ # Hata Kodları Tüm hatalar standart zarf içinde `error.code` (İngilizce slug) ve `error.message` (insan-okunur, Türkçe) ile döner. Kod (slug) programatik kontrol için stabildir; mesaj metni değişebilir. ```json { "success": false, "error": { "code": "validation_error", "message": "Girdi doğrulaması başarısız.", "details": { "field": "period" } }, "meta": { "trace_id": "...", "timestamp": "..." } } ``` ## Genel HTTP Hataları | HTTP | `code` | Anlamı | |---|---|---| | 400 | `invalid_input` | Geçersiz veya eksik girdi | | 400 | `missing_idempotency_key` | Zorunlu `Idempotency-Key` başlığı yok | | 401 | `unauthorized` | Kimlik doğrulama başarısız (eksik/hatalı key veya secret) | | 403 | `forbidden` | Erişim yetkisi yok (örn. servis sahibi değilsiniz) | | 404 | `not_found` | Kaynak bulunamadı | | 405 | `method_not_allowed` | HTTP metodu bu uç noktada desteklenmiyor | | 409 | `conflict` | Çakışan istek (örn. downgrade denemesi) | | 422 | `validation_error` | İş kuralı doğrulaması başarısız | | 429 | `rate_limited` | İstek limiti aşıldı (bkz. Rate Limit) | | 500 | `internal_error` | Sunucu tarafı beklenmeyen hata | ## Alan (Domain) Hataları | HTTP | `code` | Anlamı | |---|---|---| | 402/422 | `insufficient_credit` | Kredi bakiyesi işlem için yetersiz | | 422 | `idempotency_key_reuse` | Aynı `Idempotency-Key` farklı gövde ile kullanıldı | | 422 | `location_locked` | Yükseltme sırasında lokasyon değiştirilemez | | 404 | `webhook_not_found` | Webhook endpoint kaydı bulunamadı | | 409 | `webhook_limit_exceeded` | Bayi başına maksimum aktif webhook sayısı aşıldı | ## Nasıl Ele Almalı? - **Programatik kontrolde daima `error.code`'a bakın**, mesaj metnine değil. - `5xx` ve `429` hatalarında tekrar deneme (retry) mantıklıdır; `4xx` hatalarında (429 hariç) istek düzeltilmeden tekrar denemeyin. - Destek talebinde `meta.trace_id` değerini paylaşın — sunucu loglarında ilgili isteği bulmayı sağlar. ------------------------------------------------------------------------------ # Sipariş Yaşam Döngüsü Bir VPS siparişi oluşturmaktan servisin aktif hale gelmesine kadar geçen akış asenkrondur: sipariş anında oluşur, gerçek VPS kurulumu arka planda tamamlanır. ## 1. Sipariş Oluşturma — `POST /orders` `Idempotency-Key` **zorunludur** (çift sipariş koruması). ```bash curl -X POST https://api.rabisu.com/api/v1/orders \ -u "rsk_live_xxx:rsks_yyy" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 9f1c2d3e-4b5a-6c7d-8e9f-0a1b2c3d4e5f" \ -d '{ "product_id": 101, "period": "monthly", "config_options": { "12": 3, "15": 1 }, "extra": { "domain": "vps01.musteri.com" } }' ``` Yanıt (202 Accepted) bir **provizyon kaydı** döner. `id` alanı bayi tarafındaki korelasyon anahtarınızdır (yerel kaydınızda saklayın): ```json { "success": true, "data": { "id": 5001, "status": "pending_provision", "order_id": 88123, "service_id": null, "payment_applied": true }, "meta": { "trace_id": "...", "timestamp": "..." } } ``` ## 2. Kredi Uygulama (auto_apply_credit) Sipariş oluşturulunca fatura kesilir. `auto_apply_credit` bayrağınıza göre: - **Açık (varsayılan):** Fatura otomatik olarak bayi kredinizden ödenir. Yeterli kredi varsa provizyon kuyruğa girer (`pending_provision`). - **Kapalı:** Kredi uygulanmaz, sipariş `pending_payment` kalır. Ödemeyi sonra manuel olarak yaparsınız (bkz. adım 4). Bu, kurulum öncesi inceleme penceresi sağlar (fraud/hostname kontrolü vb.). Kredi yetersizse `insufficient_credit` bilgisi döner ve sipariş `pending_payment` durumunda bekler. ## 3. Provizyon Durumu — `GET /orders/{id}` VPS kurulumu tamamlanınca `service_id` atanır ve durum `active` olur. Durumu sorgulayın: ```bash curl -u "rsk_live_xxx:rsks_yyy" \ https://api.rabisu.com/api/v1/orders/5001 ``` Provizyon durumları: | Durum | Anlamı | |---|---| | `pending_payment` | Ödeme bekleniyor (kredi uygulanmadı) | | `pending_provision` | Ödeme tamam, VPS kurulumu kuyrukta | | `active` | VPS kuruldu, `service_id` atandı | | `failed` | Provizyon başarısız | > **İpucu:** Sürekli polling yerine Webhooks kullanın — > `service.created` olayı VPS hazır olduğunda anında bildirilir. ## 4. Bekleyen Ödemeyi Tahsil Etme — `POST /orders/{id}/settle` Sipariş `pending_payment` durumundaysa (kredi yetersizdi veya auto-apply kapalıydı), bakiye yükledikten sonra ödemeyi tetikleyin: ```bash curl -X POST https://api.rabisu.com/api/v1/orders/5001/settle \ -u "rsk_live_xxx:rsks_yyy" ``` Bu uç nokta doğal olarak idempotenttir: zaten ödenmiş bir sipariş `already_applied` ile no-op döner. Başarılı tahsil sonrası provizyon kuyruğa girer. ## 5. Servisi Yönetme Servis `active` olduktan sonra servis uç noktalarını kullanabilirsiniz: - `GET /services/{id}` — özet (IP, hostname, durum) - `GET /services/{id}/status` — anlık CPU/RAM/disk - `POST /services/{id}/power/{action}` — start/stop/restart/poweroff - `POST /services/{id}/rebuild` — işletim sistemi yeniden kurulumu - `POST /services/{id}/upgrade` — kaynak yükseltme (prorata fark faturası) - `POST /services/{id}/suspend` · `/unsuspend` — askıya al / kaldır - `DELETE /services/{id}` — iptal (disk silinir, `Idempotency-Key` zorunlu) Tüm alan detayları için OpenAPI referansına bakın. ------------------------------------------------------------------------------ # Webhooks Webhook'lar, sunucunuzu sürekli sorgulamak (polling) yerine olayları anlık olarak almanızı sağlar. Bir olay gerçekleştiğinde Rabisu, kayıtlı URL'nize imzalı bir `POST` isteği gönderir. ## 1. Endpoint Kaydı — `POST /webhooks` ```bash curl -X POST https://api.rabisu.com/api/v1/webhooks \ -u "rsk_live_xxx:rsks_yyy" \ -H "Content-Type: application/json" \ -d '{ "url": "https://bayi.com/modules/addons/rabisu/webhook.php", "event_types": ["service.*", "credit.added"], "description": "Üretim webhook" }' ``` Yanıtta HMAC **secret yalnızca bir kez** döner — güvenli saklayın: ```json { "success": true, "data": { "id": 12, "url": "https://bayi.com/.../webhook.php", "event_types": ["service.*", "credit.added"], "secret": "whsec_...TEK_SEFER...", "is_active": true }, "meta": { "trace_id": "...", "timestamp": "..." } } ``` ### URL Kuralları (SSRF Koruması) - `https://` zorunludur. - URL'de `user:pass@` bulunamaz. - DNS çözümlenen tüm IP'ler public olmalıdır (özel/dahili IP'ler reddedilir). ## 2. Abonelik Pattern'leri `event_types` dizisi şu biçimleri destekler: | Pattern | Anlamı | |---|---| | `*` | Tüm olaylar (catch-all) | | `service.*` | Namespace wildcard — `service` ile başlayan tüm olaylar | | `service.created` | Tam olay adı (exact) | Geçerli namespace'ler: `service`, `product`, `credit`. Geçersiz bir pattern gönderirseniz kayıt `422` ile reddedilir (sessizce "hiç olay almama" yerine net hata). ## 3. Olay Kataloğu | Olay | Ne zaman | Payload alanları (özet) | |---|---|---| | `service.created` | VPS provizyonu tamamlandı (aktif) | `provision_id`, `service_id`, `order_id`, `product_id`, `status` | | `service.suspended` | Servis askıya alındı | `service_id`, `reason` | | `service.unsuspended` | Askı kaldırıldı | `service_id` | | `service.terminated` | Servis iptal edildi | `service_id` | | `product.price_changed` | Ürün fiyatı değişti (bildirim) | `product_id` | | `product.stock_changed` | Ürün stok durumu değişti | `product_id`, `stock_active` | | `product.disabled` | Ürün satışa kapatıldı | `product_id` | | `credit.added` | Bayi kredisi eklendi (iade/manuel) | `reseller_id`, `amount`, `currency`, `reason`, `new_balance` | > **Not:** `product.*` olayları yalnızca **bildirim** taşır. Güncel veriyi > `GET /products/{id}` ile çekin (hibrit notification + pull). Her payload'ın kökünde `schema_version` alanı bulunur (şu an `1`). ## 4. İmza Doğrulama (KRİTİK) Her teslimatta iki imza başlığı gönderilir: | Başlık | İçerik | Kullanım | |---|---|---| | `X-Rabisu-Signature-V2` | `t=,v1=` — `HMAC-SHA256(secret, "{t}.{X-Rabisu-Event-Type}.{ham gövde}")` | **Önerilen.** Replay ve olay tipi değiştirme koruması sağlar | | `X-Rabisu-Signature` | `sha256=` — yalnızca ham gövdenin HMAC-SHA256'sı | Eski alıcılar için. Replay koruması **yok**, yeni entegrasyonlarda kullanmayın | Alıcı, `|şimdi - t| <= 300` saniye olduğunu ve imzalanan olay tipini (`X-Rabisu-Event-Type`) doğrulamalıdır. Doğrulama (PHP, V2): ```php $raw = file_get_contents('php://input'); $eventType = $_SERVER['HTTP_X_RABISU_EVENT_TYPE'] ?? ''; $header = $_SERVER['HTTP_X_RABISU_SIGNATURE_V2'] ?? ''; parse_str(str_replace(',', '&', $header), $parts); // t=..., v1=... $t = (int) ($parts['t'] ?? 0); $v1 = (string) ($parts['v1'] ?? ''); if (abs(time() - $t) > 300) { http_response_code(401); // Zaman penceresi dışında (replay) exit; } $expected = hash_hmac('sha256', $t . '.' . $eventType . '.' . $raw, $webhookSecret); if (!hash_equals($expected, $v1)) { http_response_code(401); // Geçersiz imza exit; } // Güvenli: payload işlenebilir $payload = json_decode($raw, true); ``` - İmzayı **her zaman** doğrulayın — yoksa sahte istekler kabul edersiniz. - Karşılaştırmada `hash_equals` gibi **sabit zamanlı** fonksiyon kullanın. - Gövdeyi JSON'a çevirmeden **önce** ham hali üzerinden doğrulayın (yeniden serialize edilen JSON imzayı bozar). ## 5. Teslim ve Yanıt Sözleşmesi Endpoint'iniz aşağıdaki HTTP kodlarını döndürmelidir: | Yanıt | Anlamı | Rabisu davranışı | |---|---|---| | `2xx` | Başarıyla işlendi | Teslim tamam | | `4xx` (401 dahil) | Kalıcı hata (imza/format) | Tekrar denenmez | | `5xx` | Geçici hata | Üstel backoff ile tekrar denenir | Yeniden deneme takvimi: 1dk → 5dk → 15dk → 1s → 6s → 24s. 6 denemeden sonra olay "dead letter" olarak işaretlenir. Art arda 5 başarısızlıkta endpoint otomatik pasifleştirilir (circuit breaker) — düzeltince `PATCH /webhooks/{id}` ile yeniden aktifleştirin. ## 6. Yönetim Uç Noktaları - `GET /webhooks` — kayıtlı endpoint'leri listele (pasif dahil) - `GET /webhooks/{id}` — detay - `PATCH /webhooks/{id}` — yeniden aktifleştir / `event_types` veya `description` güncelle - `DELETE /webhooks/{id}` — sil (soft delete) ============================================================================== BOLUM 2 — API REFERANSI (ENDPOINT'LER) ============================================================================== Rabisu Sanal Sunucu Bayilik (Reseller) API'si — bayilerin programatik olarak sanal sunucu siparisi vermesi, mevcut hizmetleri yonetmesi ve hesap bakiyesini sorgulamasi icin REST arayuzu. **Authentication:** HTTP Basic Auth (RFC 7617). Username = API Key (`rsk_live_*`), Password = API Secret (`rsks_live_*`). **Rate Limit:** Her API anahtarinin limiti AYRIDIR (ortak/global limit yok). Limit uc grubuna (kova) gore dakika basinadir: `default` 60 (siparis, urun, bakiye, webhook, vnc, kimlik bilgisi, lifecycle), `services_read` 150 (`GET /services`, `GET /services/{id}`), `services_status` 300 (`GET /services/{id}/status`), `services_changes` 120 (power, rebuild, parola, upgrade). Buyuk bayiye kova bazli artis Rabisu tarafindan tanimlanir; etkin degerler `GET /auth/me` → `rate_limits`. Tum yanitlarda `X-RateLimit-*` header'lari, asimda `429` + `Retry-After` (saniye) doner — istemci o kadar bekleyip tekrar denemelidir. **Idempotency:** POST/PUT/PATCH endpoint'leri `Idempotency-Key` header'ini destekler (RFC draft-irsdl-httpbis-idempotency-header). Ayni key + ayni body 24 saat icinde tekrar gonderilirse cached response doner. Ayni key + farkli body → 422 `idempotency_key_reuse`. ============================================================================== [System] ============================================================================== Saglik kontrolu, versiyon bilgisi (auth gerektirmez) ------------------------------------------------------------------------------ GET /health Saglik kontrolu (auth gerektirmez) ------------------------------------------------------------------------------ operationId: getHealth Kimlik dogrulama: Gerekmez Sunucunun ayakta oldugunu dogrular. Load balancer / monitoring icin kullanilir. Auth, rate limit ve idempotency middleware'lerine BAGLI DEGILDIR — sinirsiz cagirilabilir. Yanitlar: 200 — Sunucu saglikli Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (object, opsiyonel) - status (string, opsiyonel) [Ornek: "ok"] - version (string, opsiyonel) [Ornek: "1.0.0"] - timestamp (string(date-time), opsiyonel) [Ornek: "2026-04-24T12:00:00Z"] ------------------------------------------------------------------------------ GET /openapi.yaml OpenAPI 3.0 spec'inin ham YAML icerigi ------------------------------------------------------------------------------ operationId: getOpenApiSpec Kimlik dogrulama: Gerekmez Bu API'nin OpenAPI 3.0 spec'ini ham YAML olarak doner. Auth gerektirmez — endpoint listesi ve schema'lar dokuman karakteri tasir, gizli degil. Kullanim alanlari: - Dev test paneli (`/dev/docs.html`) Swagger UI ile bunu render eder. - Postman / Insomnia gibi araclara import. - SDK / istemci kodu jeneratorleri (openapi-generator vb.). **Cache:** ETag + 5 dakika `Cache-Control: public, max-age=300`. Spec siklikla degismez; tarayici 304 Not Modified ile ucuz cevap alir. Yanitlar: 200 — OpenAPI 3.0 spec'inin YAML icerigi Yanit basliklari: ETag: 16 karakterli MD5 prefix; If-None-Match ile 304 dondurmek icin kullanilir. | Cache-Control (application/yaml) string [Ornek: "openapi: 3.0.3\ninfo:\n title: Rabisu Reseller API\n version: 1.0.0\n...\n"] 304 — Spec degismedi (If-None-Match esleslti). Body bos. 404 — docs/openapi.yaml dosyasi sunucuda bulunamadi (deploy hatasi) (text/plain) string [Ornek: "openapi.yaml not found"] ============================================================================== [Auth] ============================================================================== Kimlik dogrulama smoke test + key meta bilgisi + kredi bakiye ------------------------------------------------------------------------------ GET /auth/me Authenticated key meta bilgisini doner ------------------------------------------------------------------------------ operationId: getAuthMe Kimlik dogrulama: BasicAuth HTTP Basic Auth ile gelen request'in dogrulanmis kimligini doner. Smoke test endpoint'i — entegrasyonun calistigini hizli test etmek icin. DONULEN ALANLAR (api_secret_hash veya benzeri sensitive alan **YOK**): - `api_key_id`: Internal numeric ID - `reseller_id`: WHMCS `tblclients.id` (bayinin musteri id'si) - `name`: Key etiketi - `permissions`: Scope listesi (`["orders:create", "*"]`) - `client_ip`: Bu istegi yapan istemci IP'si (X-Forwarded-For trusted proxy resolver ile) - `last_used_at`: Bu key'in son kullanim zamani (RFC 3339) - `expires_at`: Key'in expire tarihi (NULL = sonsuz) - `rate_limit_rpm`: Eski tek-sayi override; yalniz `default` kovayi etkiler (NULL = 60) - `rate_limits`: Kova bazli ETKIN dakika limitleri (`{"default":60,"services_read":150,"services_status":300,"services_changes":120}`) - `auto_apply_credit`: POST /orders bakiye otomatik tahsilat toggle'i (true = default; false ise siparis `pending_payment` + `notice=auto_apply_credit_disabled` doner, bayi faturayi manuel oder). Admin CLI ile degistirilir: `php bin/api-key-update.php --auto-apply-credit=0|1` Yanitlar: 200 — Authenticated, kimlik bilgisi donuldu Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (AuthMeResponse, opsiyonel) 401 — Authentication basarisiz — eksik header, gecersiz API key/secret, revoked veya expired key. Yanit basliklari: WWW-Authenticate: RFC 7235 header — istemciye Basic Auth zorunlulugunu bildirir → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "invalid_credentials", "message": "Gecersiz kimlik bilgileri." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 403 — IP whitelist ihlali — gelen IP key'in izin listesinde degil → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "ip_not_whitelisted", "message": "Bu IP adresinden erisim izniniz bulunmamaktadir." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } ------------------------------------------------------------------------------ GET /auth/balance Bayi WHMCS kredi bakiyesi (APCu 60s cache) ------------------------------------------------------------------------------ operationId: getAuthBalance Kimlik dogrulama: BasicAuth Authenticated bayinin WHMCS kredi bakiyesini doner. Bakiye ONCE read-only WHMCS DB replica'sindan (`tblclients.credit`) okunur; DB erisilemezse WHMCS HTTP API (`GetClientsDetails`) fallback'e duser. **Cache:** APCu 60 saniye (env: `BALANCE_CACHE_TTL_SECONDS`, 0 = bypass). Response'ta `cached` alani cache hit durumunu bildirir. **Currency:** Faz 1'de global `BALANCE_DEFAULT_CURRENCY` (default `TRY`). Faz 2'de per-client currency WHMCS `tblcurrencies` join ile gelecek. **Normalize:** WHMCS'de client bulunamazsa `credit=0.0` doner (404 vermez — entegrasyon tarafinin defensive programlamasi icin daha guvenli default). Yanitlar: 200 — Bakiye donuldu Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (BalanceResponse, opsiyonel) Ornek: { "success": true, "data": { "reseller_id": 1024, "credit": 125.5, "currency": "TRY", "cached": false }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 401 — Authentication basarisiz → bkz. sema: ErrorResponse 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } ------------------------------------------------------------------------------ GET /auth/balance/movements Bayi bakiye hareketleri (canli WHMCS tblcredit) ------------------------------------------------------------------------------ operationId: listAuthBalanceMovements Kimlik dogrulama: BasicAuth Authenticated bayinin bakiye hareketlerini kronolojik (yeni → eski) doner. Kaynak: **canli WHMCS `tblcredit`** — WHMCS Credit Management ile birebir (tum kredi girisleri + kullanilan/faturaya uygulanan kredi dahil). **Onemli:** Guncel bakiye icin `GET /auth/balance` kullanin. Bu liste dogrudan WHMCS'ten okundugu icin yerel senkron gecikmesi yoktur; WHMCS replica gecici erisilemezse liste bos donebilir. **`reason` degerleri:** `manual_credit` (admin kredi yuklemesi), `refund` (faturaya bagli iade, `related_invoice_id` dolu), `apply_credit` (krediden yapilan odeme, negatif `amount`). **Izolasyon:** Bayi yalnizca kendi hareketlerini gorur (client id filtresi). **`amount` isareti:** Pozitif = ekleme (kredi yuklendi/iade), Negatif = cikis (krediden odeme). `new_balance` o hareket dahil kumulatif bakiyedir. `currency` bayinin gercek WHMCS para birimidir (`tblclients.currency`). **NOT:** Yerel `mod_reseller_credit_movements` tablosu artik yalniz `credit.added` webhook'u icin beslenir; bu liste oradan OKUNMAZ. Parametreler: - limit (query, integer, opsiyonel) [varsayilan: 50; min 1; max 200] Sayfa basina kayit (1-200) - offset (query, integer, opsiyonel) [varsayilan: 0; min 0] Atlanacak kayit sayisi (sayfalama) Yanitlar: 200 — Hareket listesi donuldu Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (object, opsiyonel) - items (array, opsiyonel) Ornek: { "success": true, "data": { "items": [ { "id": 4821, "amount": -99, "currency": "TRY", "reason": "apply_credit", "description": "Siparis #5012 / fatura #8841 icin bakiyeden otomatik odeme", "related_invoice_id": 8841, "related_order_id": 5012, "new_balance": 26.5, "occurred_at": "2026-05-29T13:05:11Z", "created_at": "2026-05-29T13:05:11Z" } ] }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-05-29T13:05:12Z" } } 401 — Authentication basarisiz → bkz. sema: ErrorResponse 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } ============================================================================== [Products] ============================================================================== Urun katalogu (WHMCS urunleri + /ozl/active.json stok durumu) ------------------------------------------------------------------------------ GET /products Urun katalogu listesi (+ stok aktiflik flag'i) ------------------------------------------------------------------------------ operationId: getProducts Kimlik dogrulama: BasicAuth WHMCS `tblproducts` kaydi ile `/ozl/active.json` stok durumunu birlestirir. `hidden=1` WHMCS urunleri listeden OTOMATIK filtrelenir. **Resellable filtresi (Rabisu Master modul):** Rabisu ana WHMCS'indeki `mod_rabisu_master_product_meta` tablosunda `resellable=1` olan urunler donuler. Master modul KURULU DEGILSE (tablo yok) filtre uygulanmaz — tum gorunur urunler doner (geri uyum). **service_type + management (Rabisu Master modul):** Rabisu admini her urune tip (`vps`, `dedicated`, `hosting`) ve yonetim paneli secer. Tip bayiye `service_type` olarak doner (panel adi verilmez). `management`: `full` = API paneli yonetir (durum, guc, reinstall...); `basic` = API'de o panelin entegrasyonu yok, urun yine satilir ve bayi musterisine yalniz erisim bilgileri (`GET /services/{id}/credentials`: IP, kullanici, sifre, `panel_url`) gosterilir. Tipi secilmemis urunler listelenmez ve siparis edilemez. Bayi modulu tanimadigi tip/yonetim ikilisini import etmemelidir. **stock_active** flag'i: - `global_status=0` ise TUM urunler icin false - `products` map'i verildiyse sadece `1` olan urunler true - `products` map'i VERILMEDI ise hepsi true (fail-open) **group_id** query parametresi (opsiyonel, positive integer): sadece ilgili WHMCS urun grubunu listele. Gecersiz/bos deger YOK sayilir. Parametreler: - group_id (query, integer, opsiyonel) [min 1] WHMCS `tblproducts.gid` filtresi Yanitlar: 200 — Urun listesi donuldu Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (ProductListResponse, opsiyonel) 401 — Authentication basarisiz → bkz. sema: ErrorResponse 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } ------------------------------------------------------------------------------ GET /products/{id} Tek urun detayi ------------------------------------------------------------------------------ operationId: getProductById Kimlik dogrulama: BasicAuth Belirli bir urunun detaylarini dondurur. Urun bulunamazsa VEYA bayiye gizliyse (hidden=1) VEYA satilamaz (Rabisu Master `resellable=0`) VEYA hizmet tipi secilmemisse (`service_type`) ise AYNI 404 `product_not_found` yaniti doner — bayiler arasi envanter sizintisini onlemek icin durumlar ayrilmaz. **config_options[].role (Rabisu Master modul):** Her config option grubu `mod_rabisu_master_configgroup_meta`'dan gelen bir `role` alaniyla doner (2 rol): `resource` (RAM/CPU/disk = yukseltilebilir), `fixed` (kilitli/sabit, LOKASYON DAHIL — upgrade'de degismez). Kaydi olmayan grup veya master modul kurulu degilse `fixed` (guvenli varsayilan) doner. Eski `location` degeri de `fixed` (kilitli) sayilir. Bayi modulu + upgrade korumasi bu rolu tuketir. **config_options[].options[].stock_active (Rabisu Master modul):** Her alt-secenek (orn. tekil lokasyon / OS surumu) `mod_rabisu_master_configoption_stock`'tan gelen bir `stock_active` bool alaniyla doner. `false` ise o alt-secenek bayilere satista KAPALIDIR — bayi modulu katalogda gizler, sipariste secilirse 409 `config_option_out_of_stock` doner. Kaydi olmayan alt-secenek / master modul kurulu degilse `true`. WHMCS `hidden` bayragindan BAGIMSIZ (yalniz bayi satisini kapatir; Rabisu kendi satisina devam eder). Parametreler: - id (path, integer, zorunlu) [min 1] WHMCS `tblproducts.id` Yanitlar: 200 — Urun donuldu Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (ProductDetail, opsiyonel) 401 — Authentication basarisiz → bkz. sema: ErrorResponse 404 — Urun bulunamadi veya bayiye gizli → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "product_not_found", "message": "Aradiginiz urun bulunamadi.", "details": { "product_id": 9999 } }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } ============================================================================== [Orders] ============================================================================== Yeni VPS siparisi, mevcut siparis listesi/detayi ------------------------------------------------------------------------------ GET /orders Bayinin siparis listesi (provision kayitlari) ------------------------------------------------------------------------------ operationId: listOrders Kimlik dogrulama: BasicAuth Parametreler: - limit (query, integer, opsiyonel) [varsayilan: 50; min 1; max 200] - offset (query, integer, opsiyonel) [varsayilan: 0; min 0] Yanitlar: 200 — Siparis listesi → bkz. sema: OrderListResponse 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } ------------------------------------------------------------------------------ POST /orders Yeni siparis olustur + bakiyeden OTOMATIK odeme dene ------------------------------------------------------------------------------ operationId: createOrder Kimlik dogrulama: BasicAuth **3 sonuclu akis:** 1. `StockService::isAvailable(pid)` = false ise 409 `product_unavailable`. 1b. `extra.configoptions` icinde bayi satisi KAPALI bir alt-secenek (orn. stogu biten lokasyon) secilmisse 409 `config_option_out_of_stock` (Rabisu Master `mod_rabisu_master_configoption_stock.in_stock=0`). Bayi modulu bunlari zaten gizler; bu sunucu tarafi ikinci savunma hattidir. 2. `period` whitelist disi ise 422 `invalid_period`. 3. WHMCS `AddOrder` -> order + unpaid invoice uretilir. 4. Bayinin `auto_apply_credit` toggle'i (GET /auth/me) kontrol edilir: - **false** -> `ApplyCredit` ATLANIR; `status=pending_payment`, `payment_applied=false`, `notice=auto_apply_credit_disabled`. Bayi faturayi kendi tercihine gore (panelden/havaleden) manuel oder. Toggle admin CLI ile acilir: `php bin/api-key-update.php --auto-apply-credit=1`. - **true (default)** -> WHMCS `ApplyCredit` (TEK cagri) sonuca gore: - **success** -> `status=pending_provision`, `payment_applied=true`; WHMCS InvoicePaid hook'u virtualizor queue cron'unu tetikler. - **insufficient funds** -> `status=pending_payment`, `notice=insufficient_credit`; bayi manuel odeyebilir. - **HTTP 5xx / timeout** -> `status=payment_failed`, RequestLogger'a `alarm` flag'i ile kayit acilir. **ÇAĞRILMAYAN aksiyonlar:** `AcceptOrder` (WHMCS InvoicePaid hook zaten devreye girer), `VirtualizorClient` (WHMCS cron VPS'i kurar), bakiye pre-check, `DeleteOrder` (insufficient edge case'inde bile). **Idempotency-Key header ZORUNLUDUR** (aksi halde 400 `missing_idempotency_key`). Ayni key+body replay icin 202 cached response doner. Parametreler: - Idempotency-Key (header, string, zorunlu) [maxLength 64; Ornek: "a3f2b1e4-00d6-4c89-9fef-1a2b3c4d5e6f"] Zorunlu replay koruma anahtari (UUIDv4 onerilir) Istek govdesi (application/json, zorunlu): → bkz. sema: CreateOrderRequest Ornek: { "product_id": 101, "period": "monthly", "extra": { "hostname": "web01.bayi.example", "configoption1": "centos-stream-9" } } Yanitlar: 202 — Siparis DB'ye yazildi. `status` ApplyCredit sonucunu yansitir; bayi VPS'in kurulmasini `GET /orders/{id}` ile polla. Yanit basliklari: X-Idempotent-Replay: Cache'den donen tekrarli istekte `"true"` olur. → bkz. sema: CreateOrderResponse Ornek — pending_provision_balance_sufficient: Bakiye yeterli — ApplyCredit success { "success": true, "data": { "id": 77, "order_id": 9001, "invoice_id": 8001, "invoice_total": 99, "currency": "TRY", "status": "pending_provision", "payment_applied": true, "balance_after": 26, "notice": null, "due_date": null }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } Ornek — pending_payment_insufficient_credit: Bakiye yetersiz — fatura WHMCS'te unpaid kaliyor { "success": true, "data": { "id": 78, "order_id": 9002, "invoice_id": 8002, "invoice_total": 499, "currency": "TRY", "status": "pending_payment", "payment_applied": false, "balance_after": null, "notice": "insufficient_credit", "due_date": null }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440001", "timestamp": "2026-04-24T12:00:00Z" } } Ornek — payment_failed_apply_credit_5xx: ApplyCredit 5xx/timeout — admin mudahale gerekli { "success": true, "data": { "id": 79, "order_id": 9003, "invoice_id": 8003, "invoice_total": 199, "currency": "TRY", "status": "payment_failed", "payment_applied": false, "balance_after": null, "notice": null, "due_date": null }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440002", "timestamp": "2026-04-24T12:00:00Z" } } Ornek — pending_payment_auto_apply_credit_disabled: Bayinin auto_apply_credit toggle'i KAPALI — ApplyCredit hic cagrilmadi, bayi faturayi manuel oder. { "success": true, "data": { "id": 80, "order_id": 9004, "invoice_id": 8004, "invoice_total": 99, "currency": "TRY", "status": "pending_payment", "payment_applied": false, "balance_after": null, "notice": "auto_apply_credit_disabled", "due_date": null }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440003", "timestamp": "2026-04-24T12:00:00Z" } } 400 — Idempotency-Key header eksik Ornek: { "success": false, "error": { "code": "missing_idempotency_key", "message": "POST /orders icin Idempotency-Key header zorunludur.", "details": { "header": "Idempotency-Key" } }, "meta": { "trace_id": "...", "timestamp": "..." } } 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 409 — Urun stokta degil (`product_unavailable`) VEYA secilen config alt-secenegi bayi satisina kapali (`config_option_out_of_stock`). Ornek — product_unavailable { "success": false, "error": { "code": "product_unavailable", "message": "Secilen urun su anda siparise kapali.", "details": { "product_id": 101 } }, "meta": { "trace_id": "...", "timestamp": "..." } } Ornek — config_option_out_of_stock { "success": false, "error": { "code": "config_option_out_of_stock", "message": "Secilen secenek (orn. lokasyon) su anda stokta degil. Lutfen farkli bir secenek secin.", "details": { "sub_option_ids": [ 701 ] } }, "meta": { "trace_id": "...", "timestamp": "..." } } 422 — period whitelist disi veya body validasyon hatasi Ornek — invalid_period { "success": false, "error": { "code": "invalid_period", "message": "Gecersiz abonelik donemi.", "details": { "provided": "weekly", "allowed_values": [ "monthly", "quarterly", "semiannually", "annually", "biennially", "triennially" ] } }, "meta": { "trace_id": "...", "timestamp": "..." } } Ornek — validation_failed { "success": false, "error": { "code": "validation_failed", "message": "Siparis request body gecersiz.", "details": { "fields": { "product_id": "integer ve >0 olmalidir" } } }, "meta": { "trace_id": "...", "timestamp": "..." } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 500 — WHMCS AddOrder upstream hatasi (order yaratilmadi) ------------------------------------------------------------------------------ GET /orders/{id} Tek siparis detayi (bayi izolasyonu zorunlu) ------------------------------------------------------------------------------ operationId: getOrder Kimlik dogrulama: BasicAuth `unknown_state` kayitta bu cagri WHMCS `GetOrders` ile client ve 15 dakikalik pencereye bakar. Tek eslesen siparis `pending_payment` olarak baglanir. Pencere dolup eslesme yoksa yerel kayit `provision_failed` olur (`notice=order_not_confirmed`). `AddOrder` tekrarlanmaz. Parametreler: - id (path, integer, zorunlu) [min 1] Yanitlar: 200 — Siparis detayi → bkz. sema: OrderItem 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Siparis bulunamadi (diger bayinin kaydi da 404) Ornek: { "success": false, "error": { "code": "order_not_found", "message": "Siparis bulunamadi." }, "meta": { "trace_id": "...", "timestamp": "..." } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } ------------------------------------------------------------------------------ POST /orders/{id}/settle Bekleyen odemeyi krediyle tahsil et (InvoicePaid) ------------------------------------------------------------------------------ operationId: settleOrder Kimlik dogrulama: BasicAuth Bekleyen (`pending_payment` / `payment_failed`) bir siparisin WHMCS faturasina bayi bakiyesinden odeme dener. Bayi WHMCS'inde musteri faturayi odeyince tetiklenir (InvoicePaid hook). Siparis aninda bakiye yetersizdiyse, bayi sonradan bakiye yukledikten sonra burada tahsil edilir → VPS kurulumu kuyruga girer (`pending_provision`). **Idempotent:** Zaten odenmis (`payment_applied=true`) bir sipariste WHMCS'e hic dokunulmaz; `already_applied=true` ile mevcut durum doner. `Idempotency-Key` header'i opsiyoneldir; gonderilirse ayni anahtar ayni yaniti verir. **Esli zamanli tahsil:** Ayni siparis icin baska bir tahsil suruyorsa 409 `settle_in_progress` (Retry-After 5) doner. ApplyCredit sonucu belirsiz kalan (transport hatasi) kayit 5 dakika sonra WHMCS fatura durumuyla cozulur: `Paid` ise odenmis sayilir, `Unpaid` ise yeniden denenir. **Sonuc durumlari (`status`):** - `pending_provision` — kredi uygulandi, VPS kurulumu beklemede - `pending_payment` — bakiye hala yetersiz (`notice=insufficient_credit`), hemen tekrar denenebilir - `payment_failed` — WHMCS gecici hata (5xx); 5 dakika sonra tekrar dene - `unknown_state` / `provision_failed` — tahsil yapilmaz, `already_applied=false` **Izolasyon:** Baska bayinin siparis id'si 404 `order_not_found` doner. Parametreler: - id (path, integer, zorunlu) [min 1] `mod_reseller_provisions.id` (POST /orders'in dondurdugu id) Yanitlar: 200 — Tahsil denemesi sonucu (basari, yetersiz veya zaten odenmis) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (SettleOrderResult, opsiyonel) Ornek — applied: Kredi uygulandi { "success": true, "data": { "id": 1024, "order_id": 9401, "invoice_id": 8401, "currency": "TRY", "status": "pending_provision", "payment_applied": true, "balance_after": 10, "notice": null, "already_applied": false }, "meta": { "trace_id": "...", "timestamp": "..." } } Ornek — alreadyPaid: Zaten odenmis (no-op) { "success": true, "data": { "id": 1024, "order_id": 9402, "invoice_id": 8402, "currency": "TRY", "status": "pending_provision", "payment_applied": true, "balance_after": null, "notice": null, "already_applied": true }, "meta": { "trace_id": "...", "timestamp": "..." } } Ornek — insufficient: Bakiye hala yetersiz { "success": true, "data": { "id": 1024, "order_id": 9403, "invoice_id": 8403, "currency": "TRY", "status": "pending_payment", "payment_applied": false, "balance_after": null, "notice": "insufficient_credit", "already_applied": false }, "meta": { "trace_id": "...", "timestamp": "..." } } 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Siparis bulunamadi (diger bayinin kaydi da 404) Ornek: { "success": false, "error": { "code": "order_not_found", "message": "Siparis bulunamadi." }, "meta": { "trace_id": "...", "timestamp": "..." } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } ============================================================================== [Services] ============================================================================== VPS hizmet detayi (WHMCS + Virtualizor birlesimi), real-time metrikler ve rebuild icin OS sablonlari. Tum endpoint'ler `ServiceOwnershipMiddleware` arkasinda — bayi sadece kendi hizmetlerine erisebilir. ------------------------------------------------------------------------------ GET /services Bayinin hizmetleri — toplu liste (imlec veya sayfa + arama) ------------------------------------------------------------------------------ operationId: listServices Kimlik dogrulama: BasicAuth Bayinin tum hizmetlerini sayfa sayfa doner. Cron durum senkronu icin tasarlandi: 5000 hizmet = 25 istek (limit=200). Hizmet kaynagindan okur, Virtualizor'a GITMEZ — sunucunun acik/kapali durumu icin `GET /services/{id}/status` kullanin. **Sayfalama:** `after` imleci (son gorulen `service_id`). Ilk istek `after=0`; yanittaki `next_after` null olana kadar `?after={next_after}` ile devam edin. Imlec sayfalamasi yeni hizmet eklense de kaymaz. **Sayfa modu (admin tablosu):** `page` veya `q` gelirse liste EN YENI ONCE siralanir, `limit` (varsayilan 50) kadar oge ve `total` / `total_pages` doner. `q` hostname, ana IP ve ek IP'lerde parca eslesme yapar (`server123` → `r-server123-qwqe`); sayisalsa hizmet id'sine tam eslesme de sayilir. `match_ids` aramaya VEYA ile eklenir — bayi kendi tarafinda buldugu hizmetleri (or. musteri e-postasina gore) gonderir. Bu modda `after` ve `ids` kullanilmaz. **status:** API'nin kendi degerleri (`active`, `suspended`...); kaynak sisteme (bugun WHMCS) ozgu metin donmez. **Sahiplik:** Yalniz cagiran bayinin hizmetleri doner; `ids` filtresinde baska bayinin id'si sessizce atlanir. **Rate limit:** `services_read` kovasi (150 req/dk, `GET /services/{id}` ile ortak). Parametreler: - limit (query, integer, opsiyonel) [varsayilan: 100; min 1; max 200] - after (query, integer, opsiyonel) [varsayilan: 0; min 0] Imlec — bu service_id'den SONRAKI hizmetler - ids (query, string, opsiyonel) [Ornek: "7777,7778"] Virgulle ayrilmis service_id listesi (en fazla 200) - page (query, integer, opsiyonel) [min 1] Sayfa modu — 1'den baslar, en yeni once - q (query, string, opsiyonel) [maxLength 100; Ornek: "server123"] Sayfa modu — hostname / IP parca, id tam eslesme - match_ids (query, string, opsiyonel) [Ornek: "7777,7778"] Sayfa modu — aramaya VEYA ile eklenen service_id'ler (en fazla 500) Yanitlar: 200 — Hizmet sayfasi Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (ServiceListResponse, opsiyonel) Ornek: { "success": true, "data": { "items": [ { "service_id": 7777, "product_id": 101, "product_name": "VPS X4", "status": "active", "domain": "web01.bayi.example", "dedicated_ip": "203.0.113.42", "billing_cycle": "Monthly", "first_payment_amount": 99, "recurring_amount": 99, "next_due_date": "2026-11-24", "reg_date": "2026-04-24" } ], "currency": "TRY", "next_after": 7777 }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-10-04T12:00:00Z" } } 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 422 — Gecersiz `ids`, `match_ids` veya `q` parametresi → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "invalid_input", "message": "ids virgulle ayrilmis pozitif tam sayilar olmali (en fazla 200).", "details": { "field": "ids" } }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-10-04T12:00:00Z" } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 503 — Hizmet kaynagi gecici olarak okunamadi — Retry-After sonra tekrar deneyin Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "service_list_unavailable", "message": "Hizmet listesi su an okunamiyor, biraz sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-10-04T12:00:00Z" } } ------------------------------------------------------------------------------ GET /services/{id} VPS hizmet detayi (WHMCS + Virtualizor birlesimi) ------------------------------------------------------------------------------ operationId: getServiceById Kimlik dogrulama: BasicAuth Bayi VPS hizmetinin WHMCS-side bilgileri (domain, IP, fatura periyodu, son odeme tarihi, namespace) ile Virtualizor-side runtime metrikleri (hostname, IP listesi, virt tipi, calisma durumu) birlesir. **VPS_ID resolution:** Resmi Virtualizor WHMCS modulu VPS_ID'yi WHMCS custom field'da tutar — `tblcustomfields` (fieldname=`vpsid` veya gorunen adli `vpsid|...`, type='product', relid=PRODUCT_ID) + `tblcustomfieldsvalues` (fieldid=customfield.id, relid=SERVICE_ID, value=VPSID). Boru oncesi ad eslesir. Custom field tanimlanmamis veya value bos ise `vps_id=null` doner ve `virt` payload'i null olur (whitelabel paketler veya henuz provisionlanmamis hizmetler). **Graceful degradation:** Virtualizor master upstream hatasi (timeout, 5xx, auth failure) durumunda 502 yerine 200 doner; `virt=null` ve `virt_unavailable=true`. Bayi en azindan WHMCS bilgisini gorur. **Rate limit:** `services_read` kovasi (150 req/dk, `GET /services` ile ortak). Parametreler: - id (path, integer, zorunlu) [min 1] WHMCS `tblhosting.id` (service_id) Yanitlar: 200 — Hizmet detayi donuldu (virt_unavailable=true ise virt=null olabilir) Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (ServiceDetail, opsiyonel) Ornek: { "success": true, "data": { "service_id": 7777, "product_id": 101, "server_id": 12, "vps_id": 1234, "domain": "web01.bayi.example", "dedicated_ip": "203.0.113.42", "nameservers": [ "ns1.rabisu.com", "ns2.rabisu.com" ], "billing_cycle": "monthly", "first_payment_amount": 99, "recurring_amount": 99, "next_due_date": "2026-05-24", "domain_status": "Active", "reg_date": "2026-04-24", "virt": { "hostname": "web01.bayi.example", "ips": [ "203.0.113.42" ], "virt_type": "kvm", "suspended": false, "os_name": "almalinux-8-x86_64", "default_user": "root", "cores": 2, "ram_mb": 4096, "disk_gb": 40, "bandwidth_gb": 30720, "bandwidth_used_gb": 12.5, "location": "Germany" }, "virt_unavailable": false }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 403 — Yetki yetersiz — bayi baska bir bayinin service_id'sine erismeye calisti veya admin scope gerektiren endpoint icin yetersiz scope. Mesaj generic tutulur (ServiceOwnershipMiddleware sizinti onler). → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "forbidden", "message": "Bu kaynaga erisim yetkiniz yok." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Hizmet bulunamadi (tblhosting'de kayit yok) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "service_not_found", "message": "Servis bulunamadi.", "details": { "service_id": 7777 } }, "meta": { "trace_id": "...", "timestamp": "..." } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } ------------------------------------------------------------------------------ DELETE /services/{id} Servisi WHMCS modul hook'u ile terminate et (asenkron) ------------------------------------------------------------------------------ operationId: terminateService Kimlik dogrulama: BasicAuth WhmcsApiClient::moduleTerminate cagirilir. WHMCS provisioning queue Virtualizor `delvs` job'unu async yurutur (~60s queue + ~30s delvs). Bu yuzden response **HTTP 202 Accepted** doner; bayi sonra GET /services/{id}/status ile takip eder. **Idempotency-Key:** ZORUNLU (header yoksa `400 missing_idempotency_key`). Destructive op + double-click korumasi. Ayni key ile 24 saat icinde gelen tekrar istek cached response doner — 2. delvs queue'ya dusmez. **Rate limit:** Default bucket (60 rpm). **Body:** `{"confirm": true}` STRICT bool === true. Truthy degerler (`1`, `"true"`, `"yes"`) reddedilir → `400 invalid_terminate_request`. **vps_id null:** ATLA (izin verilir). Pending/whitelabel servisleri temizleme senaryosu — WHMCS moduleTerminate vps_id'ye bakmaz, serviceid ile calisir. **Side effect:** `mod_reseller_provisions.status='terminated'` set edilir + `service.terminated` event'i (`status='skipped'`) webhook queue'ya yazilir. Parametreler: - id (path, integer, zorunlu) [min 1] - Idempotency-Key (header, string(uuid), zorunlu) ZORUNLU. Ayni key 24h cached. Istek govdesi (application/json, zorunlu): - confirm (boolean, zorunlu) [degerler: true] STRICT bool === true. Truthy reddedilir. Ornek: { "confirm": true } Yanitlar: 202 — Termination scheduled (async — WHMCS queue + Virtualizor delvs) Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (TerminateScheduledResult, opsiyonel) Ornek: { "success": true, "data": { "status": "termination_scheduled", "service_id": 12345, "vps_id": 67890, "terminated_at": "2026-04-25T06:25:00+00:00", "estimated_completion_seconds": 120 }, "meta": { "trace_id": "...", "timestamp": "..." } } 400 — Invalid terminate request (`invalid_terminate_request` veya `missing_idempotency_key`) 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 403 — Yetki yetersiz — bayi baska bir bayinin service_id'sine erismeye calisti veya admin scope gerektiren endpoint icin yetersiz scope. Mesaj generic tutulur (ServiceOwnershipMiddleware sizinti onler). → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "forbidden", "message": "Bu kaynaga erisim yetkiniz yok." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Servis bulunamadi 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 502 — WHMCS API upstream hatasi ------------------------------------------------------------------------------ GET /services/{id}/status VPS anlik metrikleri (CPU/RAM/disk/net) ------------------------------------------------------------------------------ operationId: getServiceStatus Kimlik dogrulama: BasicAuth Virtualizor admin `act=vs&vs_status=` ile CANLI durum ve metrik (dugume o an sorar). Bu cagri hata verir ya da beklenen yapida donmezse periyodik `act=vps_stats` kullanilir. CPU yuzdesi enduser `act=cpu` ile gelir. **Rate limit:** `services_status` kovasi — 300 req/dk, bayi basina ayri. Diger kovalardan IZOLE. **management=basic:** `409 management_unavailable` (panel API'de yok). **Hata davranisi:** Virtualizor down ise 502 doner (graceful degradation YOK — real-time metric icin partial yanit anlamsiz). **Cache:** VPS basina 15 sn (sunucuda `STATUS_CACHE_TTL_SECONDS`). Ayni VPS'e bu surede gelen ikinci istek Virtualizor'a gitmez. Power, rebuild, suspend, unsuspend ve terminate islemleri onbellegi siler ve sonraki 180 sn onbellegi tamamen kapatir; gecis sirasindaki ara durum (or. stop sonrasi hala calisiyor) onbellekte kalip paneli geciktirmez. Parametreler: - id (path, integer, zorunlu) [min 1] Yanitlar: 200 — Anlik metrikler Yanit basliklari: X-RateLimit-Limit: services_status kovasi icin anahtarin etkin limiti | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (ServiceStatus, opsiyonel) Ornek: { "success": true, "data": { "service_id": 7777, "vps_id": 1234, "running": true, "cpu_percent": 12.5, "ram_used_mb": 768, "ram_total_mb": 2048, "disk_used_mb": 5500, "disk_total_mb": 40960, "net_in_bytes": 102400, "net_out_bytes": 51200, "bandwidth_total_bytes": 1256550400 }, "meta": { "trace_id": "...", "timestamp": "..." } } 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 403 — Yetki yetersiz — bayi baska bir bayinin service_id'sine erismeye calisti veya admin scope gerektiren endpoint icin yetersiz scope. Mesaj generic tutulur (ServiceOwnershipMiddleware sizinti onler). → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "forbidden", "message": "Bu kaynaga erisim yetkiniz yok." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Hizmet bulunamadi VEYA Virtualizor mapping'i yok → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "service_not_found", "message": "Servis bulunamadi.", "details": { "service_id": 7777 } }, "meta": { "trace_id": "...", "timestamp": "..." } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 502 — Virtualizor master upstream hatasi (timeout / 5xx) → bkz. sema: ErrorResponse ------------------------------------------------------------------------------ GET /services/{id}/credentials Erisim bilgileri (username/password/ip/hostname/panel_url) ------------------------------------------------------------------------------ operationId: getServiceCredentials Kimlik dogrulama: BasicAuth Bayinin VPS'inin root erisim bilgilerini doner. `password` WHMCS `DecryptPassword` API action'i ile cozulur (tblhosting.password sifreli saklanir). Cozulemezse (DB'de bos, WHMCS API izni yok, transport hatasi) `password_available=false` + bos `password` doner — bayi modulu bu durumda mevcut sifreyi EZMEMELI. **Kullanim:** Bayi modulu `service.created` webhook'u sonrasi bu endpoint'i cagirip kendi WHMCS `tblhosting` kaydina (password + dedicatedip) yazar; boylece admin/musteri panelinde + `{$service_password}` mail merge field'inde gorunur. **management=basic:** Panelini API yonetemeyen hizmette musteri bu bilgilerle dogrudan baglanir; `panel_url` = WHMCS sunucusunun hostname'i (cPanel :2083, Plesk :8443). Tam yonetimli hizmette `panel_url` null. **GUVENLIK:** Donen `password` plaintext root sifresidir; sahiplik (api_key → reseller → service) dogrulanir, loglarda maskelenir. **Rate limit:** Default bucket (60 req/dk). **Idempotency:** yok (read). Parametreler: - id (path, integer, zorunlu) [min 1] Yanitlar: 200 — VPS erisim bilgileri Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (object, opsiyonel) - service_id (integer, opsiyonel) [Ornek: 7777] - vps_id (integer, opsiyonel, null olabilir) [Ornek: 1234] - username (string, opsiyonel) [Ornek: "root"] - password (string, opsiyonel) [Ornek: "S3cr3t-Root-Pw"] Plaintext root sifresi. password_available=false ise bos. - password_available (boolean, opsiyonel) [Ornek: true] Password WHMCS'ten cozulebildi mi. - ip (string, opsiyonel) [Ornek: "203.0.113.42"] - hostname (string, opsiyonel) [Ornek: "web01.bayi.example"] - service_type (string, opsiyonel) [degerler: "vps" | "dedicated" | "hosting"; Ornek: "vps"] - management (string, opsiyonel) [degerler: "full" | "basic"; Ornek: "full"] - panel_url (string, opsiyonel, null olabilir) Yalniz management=basic — musterinin giris yapacagi panel adresi. 403 — Yetki yetersiz — bayi baska bir bayinin service_id'sine erismeye calisti veya admin scope gerektiren endpoint icin yetersiz scope. Mesaj generic tutulur (ServiceOwnershipMiddleware sizinti onler). → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "forbidden", "message": "Bu kaynaga erisim yetkiniz yok." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Kaynak bulunamadi (veya bayiye gizli). Bayiler arasi envanter/kaynak sizintisini onlemek icin "yok" ve "erisilemez" tek yanitla doner. → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "not_found", "message": "Aradiginiz kaynak bulunamadi." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } ------------------------------------------------------------------------------ GET /services/{id}/os-templates Bu VPS icin rebuild'e izinli OS sablonlari (servis-spesifik filtre) ------------------------------------------------------------------------------ operationId: listServiceOsTemplates Kimlik dogrulama: BasicAuth VPS rebuild islemi icin secim listesi. **ENH-001 (live test 2026-05-19):** liste artik servis-spesifik — Virtualizor `act=rebuild&vpsid=X` form-load cagrisi ile gelen `ostemplates` field'i parse edilir. Bu, Virtualizor'da VPS'e atanmis medya group'una gore filtrelenmis listedir. Onceden global liste (`act=ostemplates`, 255+ OS) doniyordu ve bayi yanlis OS secip rebuild yapinca Virtualizor "The OS you submitted is invalid" hatasi veriyordu. Artik sadece izinli OS'lar gosterilir. **NOT:** Ilk siparis (`POST /orders`) bu endpoint'i KULLANMAZ. Provisioning sirasinda WHMCS Virtualizor modulu kendi config options'tan OS atar. Bu endpoint sadece rebuild flow'u icindir. **Cache:** APCu, key `os_templates:v2:vps:{vpsId}` (per-VPS), TTL 300 saniye. Farkli VPS'ler farkli medya grupta olabildigi icin global key COZUM DEGIL. Ayni medya gruptaki 2 VPS bu cache'ten faydalanmaz — pratik etki sifir (OS listesi ayda bir guncellenir). **Cache invalidation:** Operator yeni OS image ekledikten sonra max 5dk icinde gorunur. Acil invalidate icin admin CLI hook (Faz 4) gelecek. **Rate limit:** Default bucket (60 req/dk). **management=basic:** `409 management_unavailable`. **vps_id null durumu:** 422 `service_not_provisioned` doner — provisioning bekleyen servis icin rebuild zaten yapilamaz. Parametreler: - id (path, integer, zorunlu) [min 1] Yanitlar: 200 — OS sablon listesi Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (OsTemplateList, opsiyonel) Ornek: { "success": true, "data": { "templates": [ { "id": 270, "name": "CentOS Stream 9", "virt_type": "kvm" }, { "id": 280, "name": "Debian 12", "virt_type": "kvm" }, { "id": 290, "name": "Ubuntu 24.04", "virt_type": "kvm" } ] }, "meta": { "trace_id": "...", "timestamp": "..." } } 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 403 — Yetki yetersiz — bayi baska bir bayinin service_id'sine erismeye calisti veya admin scope gerektiren endpoint icin yetersiz scope. Mesaj generic tutulur (ServiceOwnershipMiddleware sizinti onler). → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "forbidden", "message": "Bu kaynaga erisim yetkiniz yok." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Kaynak bulunamadi (veya bayiye gizli). Bayiler arasi envanter/kaynak sizintisini onlemek icin "yok" ve "erisilemez" tek yanitla doner. → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "not_found", "message": "Aradiginiz kaynak bulunamadi." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 422 — VPS henuz provisioning bekleniyor (vps_id null). Rebuild yapilamayacagi icin OS listesi de cekilemez. Bayi modulu provisioning tamamlanana kadar "OS listesi bekleniyor" mesaji gostermeli. Asagidakilerin birlesimi (allOf): → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "service_not_provisioned", "message": "Bu servis henuz olusturulmamis.", "details": { "service_id": 7777 } }, "meta": { "trace_id": "...", "timestamp": "..." } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } ------------------------------------------------------------------------------ POST /services/{id}/power/{action} VPS power islemi (start/stop/restart/poweroff) ------------------------------------------------------------------------------ operationId: powerService Kimlik dogrulama: BasicAuth Bayi VPS'inin guc durumunu degistirir. `action` URL parametresinde `start`, `stop`, `restart` veya `poweroff` kabul edilir; baska deger Slim route regex'inde 404 doner. **Idempotency-Key ZORUNLU.** Header eksikse `400 missing_idempotency_key` doner. Power aksiyonlari double-click korumasi icin idempotent islenmelidir; bayi modulu HER TIKLAMADA YENI bir UUID v4 uretmeli. Ayni key + ayni body 24 saat boyunca cached response replay eder (`X-Idempotent-Replay: true` header'i ile). **Rate limit:** `services_changes` bucket (120 req/dk). **vps_id null:** Servis henuz Virtualizor'a baglanmamis (`tblcustomfieldsvalues` bos veya tanimsiz) ise `422 service_not_provisioned` doner (`management=basic` hizmette `409 management_unavailable`) ve Virtualizor'a HIC cagri yapilmaz. Parametreler: - id (path, integer, zorunlu) [min 1] - action (path, string, zorunlu) [degerler: "start" | "stop" | "restart" | "poweroff"] - Idempotency-Key (header, string, zorunlu) [maxLength 64] Her tiklamada bayi modulunun urettigi YENI UUID v4 (RFC 4122). Tekrar gonderilen ayni key cached response doner. Yanitlar: 200 — Power aksiyonu islendi Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) | X-Idempotent-Replay: Cache hit replay ise `true`. Yeni isleme `header gonderilmez`. Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (PowerActionResult, opsiyonel) Ornek: { "success": true, "data": { "status": "power_action_executed", "action": "start", "vps_id": 12345, "executed_at": "2026-04-25T06:01:00+00:00" }, "meta": { "trace_id": "...", "timestamp": "..." } } 400 — Idempotency-Key eksik veya gecersiz power action Ornek — missing_idempotency_key { "success": false, "error": { "code": "missing_idempotency_key", "message": "POST /services/{id}/power/{action} icin Idempotency-Key header zorunludur.", "details": { "header": "Idempotency-Key", "endpoint": "POST /services/{id}/power/{action}" } } } 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 403 — Yetki yetersiz — bayi baska bir bayinin service_id'sine erismeye calisti veya admin scope gerektiren endpoint icin yetersiz scope. Mesaj generic tutulur (ServiceOwnershipMiddleware sizinti onler). → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "forbidden", "message": "Bu kaynaga erisim yetkiniz yok." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 422 — VPS henuz provisionlanmamis (vps_id custom field bos) Ornek: { "success": false, "error": { "code": "service_not_provisioned", "message": "VPS not yet provisioned or vpsid custom field missing", "details": { "service_id": 12345 } } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 502 — Virtualizor upstream hatasi ------------------------------------------------------------------------------ POST /services/{id}/rebuild VPS OS reinstall (asenkron, 202 Accepted) ------------------------------------------------------------------------------ operationId: rebuildService Kimlik dogrulama: BasicAuth VPS'i secilen OS template'i ile yeniden kurar. Disk uzerindeki TUM veri silinir. Asenkron job — Virtualizor `rebuildvps` islemi 5-30 dakika surer; API hemen 202 Accepted doner ve bayi modulu sonra `GET /services/{id}/status` ile takip eder. **GUVENLIK:** Body'de `confirm: true` (strict bool) ZORUNLU. `false` veya eksikse `400 confirmation_required` doner ve Virtualizor'a HIC cagri yapilmaz (client-side guard). **Idempotency-Key ZORUNLU.** **Rate limit:** `services_changes` bucket. **vps_id null:** `422 service_not_provisioned`. **management=basic:** `409 management_unavailable`. Parametreler: - id (path, integer, zorunlu) [min 1] - Idempotency-Key (header, string, zorunlu) [maxLength 64] Istek govdesi (application/json, zorunlu): - os_id (integer, zorunlu) [min 1] Hedef OS template ID. `/services/{id}/os-templates` listesinden secilir. - confirm (boolean, zorunlu) [degerler: true] Veri silinecegi onayi. `true` ZORUNLU; `false` veya eksikse `400 confirmation_required` doner. Strict bool — `1`/`'true'` kabul edilmez. - root_password (string, opsiyonel) [minLength 8; maxLength 64] **Opsiyonel** yeni root parolasi. Bayi modulu WHMCS'ten gelen veya kullanici UI'sinde belirledigi parolayi gonderebilir. Gonderilmezse API guvenli (16 char, alfanumerik) bir parola UREITR ve response'taki `data.root_password` alaninda 1 kez doner. Bayi bu degeri kullaniciya GOSTERMELIDIR — sonradan geri alinamaz (Virtualizor saklamaz). **Karakter sinirlamalari:** - Sadece ASCII yazdirilabilir (0x20-0x7E) - `'`, `"`, `` ` ``, `\\`, `$` karakterleri YASAK (shell/quote escape riski). Yanitlar: 202 — Rebuild scheduled (asenkron job baslatildi) Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) | X-Idempotent-Replay: Cache hit replay ise `true`. Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (RebuildScheduledResult, opsiyonel) Ornek: { "success": true, "data": { "status": "rebuild_scheduled", "os_id": 270, "vps_id": 12345, "estimated_duration_seconds": 600, "root_password": "Xkj9aF2pBm7nQrV3" }, "meta": { "trace_id": "...", "timestamp": "..." } } 400 — Idempotency-Key eksik / confirm=false / body validasyon hatasi Ornek — confirmation_required { "success": false, "error": { "code": "confirmation_required", "message": "VPS kurtarma islemi veri kaybina yol acar. Devam etmek icin `confirm: true` parametresini gondermelisiniz.", "details": { "vps_id": 12345, "os_id": 270 } } } Ornek — missing_idempotency_key { "success": false, "error": { "code": "missing_idempotency_key", "message": "POST /services/{id}/rebuild icin Idempotency-Key header zorunludur." } } Ornek — invalid_rebuild_request { "success": false, "error": { "code": "invalid_rebuild_request", "message": "`os_id` must be a positive integer." } } 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 403 — Yetki yetersiz — bayi baska bir bayinin service_id'sine erismeye calisti veya admin scope gerektiren endpoint icin yetersiz scope. Mesaj generic tutulur (ServiceOwnershipMiddleware sizinti onler). → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "forbidden", "message": "Bu kaynaga erisim yetkiniz yok." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 422 — VPS provisionlanmamis VEYA secilen os_id bu VPS'in media grubunda izinli degil. **os_not_allowed (guvenlik):** GET /os-templates listesi media-grup-filtreli olsa da rebuild action'i server-side dogrulanir — listede olmayan bir os_id (forge) gonderilirse Virtualizor'a HIC gidilmez, 422 doner. Ornek — service_not_provisioned { "success": false, "error": { "code": "service_not_provisioned", "message": "VPS not yet provisioned or vpsid custom field missing", "details": { "service_id": 12345 } } } Ornek — os_not_allowed { "success": false, "error": { "code": "os_not_allowed", "message": "Secilen isletim sistemi bu sunucu icin kullanilabilir degil.", "details": { "service_id": 12345, "os_id": 999 } } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 502 — Virtualizor upstream hatasi ------------------------------------------------------------------------------ POST /services/{id}/vnc-token Kisa omurlu (60s) NoVNC baglanti bilgisi ------------------------------------------------------------------------------ operationId: vncTokenService Kimlik dogrulama: BasicAuth Browser tarafinda direkt `wss://{host}:{port}/{websockify_path}` URL'sine baglanmak icin kullanilir (port = Virtualizor enduser novnc proxy portu, ornn. 4083; HAM VNC/RFB portu DEGIL). API HIC bu websocket akisina PROXY YAPMAZ — sadece baglanti bilgisi (host/port/password/expires_at) doner. **Idempotency-Key KABUL ETMEZ.** Header gelse bile IGNORE edilir (route'a IdempotencyMiddleware bagli degil). Sebep: her cagri Virtualizor'da YENI password uretir; cached eski password 60s sonra expire olur ve bayi "sifre gecersiz" hatasi alirdi. **Rate limit:** Default bucket (60 req/dk) — high-frequency degil. **vps_id null:** `422 service_not_provisioned`. **management=basic:** `409 management_unavailable`. **Response:** - `expires_at`: server'in hesapladigi UTC ISO8601 (now + 60s) - `ttl_seconds`: 60 (sabit, client kolay parse etsin) - `password`: short-lived NoVNC parolasi (LogMasker `vnc_password` alanini maskeler) - `host`/`port`/`websockify_path`: bayi browser'inin baglanacagi node bilgisi Parametreler: - id (path, integer, zorunlu) [min 1] Yanitlar: 200 — VNC baglanti bilgisi (60s gecerli) Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (VncTokenResult, opsiyonel) Ornek: { "success": true, "data": { "vps_id": 12345, "host": "node01.rabisu.com", "port": 4083, "websockify_path": "novnc/?virttoken=12345", "password": "Xkj9_short_lived", "expires_at": "2026-04-25T06:01:00+00:00", "ttl_seconds": 60 }, "meta": { "trace_id": "...", "timestamp": "..." } } 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 403 — Yetki yetersiz — bayi baska bir bayinin service_id'sine erismeye calisti veya admin scope gerektiren endpoint icin yetersiz scope. Mesaj generic tutulur (ServiceOwnershipMiddleware sizinti onler). → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "forbidden", "message": "Bu kaynaga erisim yetkiniz yok." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 422 — VPS henuz provisionlanmamis Ornek: { "success": false, "error": { "code": "service_not_provisioned", "message": "VPS not yet provisioned or vpsid custom field missing", "details": { "service_id": 12345 } } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 502 — Virtualizor upstream hatasi ------------------------------------------------------------------------------ POST /services/{id}/password VPS root parolasini degistir ------------------------------------------------------------------------------ operationId: changeServicePassword Kimlik dogrulama: BasicAuth Virtualizor `act=editvs` round-trip ile root parolasini degistirir (resmi WHMCS modulu deseni: once tam config GET, sonra `editvps=1` + `rootpass` ile POST geri). **ONEMLI:** Parolanin VPS'e ETKIN olmasi cogu sanallastirma tipinde guc dongusu (kapat/ac) gerektirir. Bu endpoint SADECE Virtualizor'a yeni parolayi yazar; bayi modulu degisiklik sonrasi `poweroff` + `start` orkestrasyonunu kendisi yapar. **Idempotency-Key:** OPSIYONEL (ayni parolayi tekrar yazmak zararsiz; rebuild gibi destructive degil). **Rate limit:** services_changes bucket. **Guvenlik:** Parola NE LOG'LANIR NE DE response'ta dondurulur. **vps_id null:** `422 service_not_provisioned`. **management=basic:** `409 management_unavailable`. Parametreler: - id (path, integer, zorunlu) [min 1] - Idempotency-Key (header, string(uuid), opsiyonel) Opsiyonel — gelirse 24h replay korumasi. Istek govdesi (application/json, zorunlu): - password (string, zorunlu) [minLength 8; maxLength 64; Ornek: "YeniGuclu_Sifre2026"] Yeni root parolasi. Sadece yazdirilabilir ASCII (0x20-0x7E), tirnak/backtick/backslash/dollar ($ ' " ` \) yasak. Yanitlar: 200 — Parola degistirildi (henuz guc dongusu gerekebilir) Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) Ornek: { "success": true, "data": { "status": "password_changed", "vps_id": 12345, "changed_at": "2026-05-31T18:00:00+00:00" }, "meta": { "trace_id": "...", "timestamp": "..." } } 400 — Gecersiz parola (uzunluk/karakter) veya body Ornek: { "success": false, "error": { "code": "invalid_change_password_request", "message": "`password` length must be between 8 and 64 characters.", "details": {} } } 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 403 — Yetki yetersiz — bayi baska bir bayinin service_id'sine erismeye calisti veya admin scope gerektiren endpoint icin yetersiz scope. Mesaj generic tutulur (ServiceOwnershipMiddleware sizinti onler). → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "forbidden", "message": "Bu kaynaga erisim yetkiniz yok." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 422 — VPS henuz provisionlanmamis Ornek: { "success": false, "error": { "code": "service_not_provisioned", "message": "VPS not yet provisioned or vpsid custom field missing", "details": { "service_id": 12345 } } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 502 — Virtualizor upstream hatasi (editvs done flag yok) ------------------------------------------------------------------------------ POST /services/{id}/suspend Servisi WHMCS modul hook'u ile suspend et ------------------------------------------------------------------------------ operationId: suspendService Kimlik dogrulama: BasicAuth WhmcsApiClient::moduleSuspend cagirilir. WHMCS kendi virtualizor modulunu tetikler, queue cron Virtualizor `suspendvs` job'una cevirir. Bizim DB'de `mod_reseller_provisions.status='suspended'` + `notice='Suspended via API: '` set edilir. **Idempotency-Key:** OPSIYONEL (header gelirse middleware uygula). Suspend zaten idempotent (zaten suspended ise WHMCS no-op). **Rate limit:** Default bucket (60 rpm) — lifecycle high-frequency degil. **vps_id null:** `422 service_not_provisioned` (provision olmayan servis suspend edilmez). `management=basic` hizmette vps_id aranmaz; WHMCS modulu uygular. **Body:** `{"reason": "..."}` opsiyonel (1-255 char). Default: `"Suspended via Reseller API"`. Bayinin WHMCS'inde gosterilen reason ile senkron olabilsin. **Side effect:** `mod_reseller_webhooks_queue` tablosuna `service.suspended` event'i (`status='skipped'`) yazilir. Faz 2 MVP'de gonderim YOK; v2 worker bu event'leri reseller webhook URL'lerine push eder. Parametreler: - id (path, integer, zorunlu) [min 1] - Idempotency-Key (header, string(uuid), opsiyonel) Opsiyonel — gelirse 24h replay korumasi. Istek govdesi (application/json): - reason (string, opsiyonel) [minLength 1; maxLength 255; Ornek: "Non-payment"] Ornek: { "reason": "Non-payment" } Yanitlar: 200 — Servis suspend edildi Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (SuspendResult, opsiyonel) Ornek: { "success": true, "data": { "status": "suspended", "service_id": 12345, "vps_id": 67890, "reason": "Non-payment", "suspended_at": "2026-04-25T06:23:00+00:00" }, "meta": { "trace_id": "...", "timestamp": "..." } } 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 403 — Yetki yetersiz — bayi baska bir bayinin service_id'sine erismeye calisti veya admin scope gerektiren endpoint icin yetersiz scope. Mesaj generic tutulur (ServiceOwnershipMiddleware sizinti onler). → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "forbidden", "message": "Bu kaynaga erisim yetkiniz yok." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Servis bulunamadi 422 — VPS henuz provisionlanmamis Ornek: { "success": false, "error": { "code": "service_not_provisioned", "message": "VPS not yet provisioned or vpsid custom field missing", "details": { "service_id": 12345 } } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 502 — WHMCS API upstream hatasi (whmcs_upstream_error) ------------------------------------------------------------------------------ POST /services/{id}/unsuspend Servisin suspend'ini WHMCS modul hook'u ile geri al ------------------------------------------------------------------------------ operationId: unsuspendService Kimlik dogrulama: BasicAuth WhmcsApiClient::moduleUnsuspend cagirilir. Bizim DB'de `mod_reseller_provisions.status='active'` + `notice='Unsuspended via API'` set edilir. **Idempotency-Key:** OPSIYONEL. **Rate limit:** Default bucket (60 rpm). **vps_id null:** `422 service_not_provisioned`. **management=basic:** vps_id aranmaz; WHMCS modulu uygular. **Body:** Yok. **Side effect:** `service.unsuspended` event'i (`status='skipped'`) webhook queue'ya yazilir. Parametreler: - id (path, integer, zorunlu) [min 1] - Idempotency-Key (header, string(uuid), opsiyonel) Yanitlar: 200 — Servis tekrar aktif Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (UnsuspendResult, opsiyonel) Ornek: { "success": true, "data": { "status": "active", "service_id": 12345, "vps_id": 67890, "unsuspended_at": "2026-04-25T06:24:00+00:00" }, "meta": { "trace_id": "...", "timestamp": "..." } } 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 403 — Yetki yetersiz — bayi baska bir bayinin service_id'sine erismeye calisti veya admin scope gerektiren endpoint icin yetersiz scope. Mesaj generic tutulur (ServiceOwnershipMiddleware sizinti onler). → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "forbidden", "message": "Bu kaynaga erisim yetkiniz yok." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Servis bulunamadi 422 — VPS henuz provisionlanmamis 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 502 — WHMCS API upstream hatasi ------------------------------------------------------------------------------ POST /services/{id}/upgrade Servisin config option'larini yukselt (upgrade) ------------------------------------------------------------------------------ operationId: upgradeService Kimlik dogrulama: BasicAuth Servisin configurable option'larini (RAM/CPU/disk gibi `resource` rolu gruplar) yeni degerlere yukseltir. WHMCS `UpgradeProduct` action'i `type=configoptions` ile cagirilir; prorata farki WHMCS tarafinda hesaplanir. **Iki mod:** - `quote_only=true` → WHMCS `calconly=1` ile SADECE fiyat teklifi doner (order/invoice olusmaz, `quote=true`). - `quote_only=false` (default) → gercek upgrade uygulanir. Fark faturasi olusur, bayi kredisi otomatik uygulanir (OrderService ApplyCredit deseniyle ayni: `pending_provision` / `pending_payment` / `payment_failed`). **Kilit bekci:** Yalniz `role=resource` gruplari (RAM/CPU/disk) upgrade ile degistirilebilir. Diger gruplar (`fixed`, eski `location` veya metadata'si olmayan → guvenli varsayilan kilitli) DEGISTIRILEMEZ; bu gruplardaki bir secim degistirilirse `422 location_locked` doner. Ayni degerde birakmak gecerlidir. **Tam config (merge):** WHMCS'e her zaman TAM config gonderilir (servisin mevcut tum secimleri + bayinin delta'lari). WHMCS gonderilmeyen bir config option'i urunun default (1.) degerine cektigi icin (yanlis lokasyon + yanlis prorata olurdu), lokasyon dahil hicbir secim resetlenmez. **Downgrade yasak:** WHMCS negatif prorata (kredi/iade) donerse `409 downgrade_not_allowed` doner. **Cakisma:** Servis icin odenmemis onceki bir upgrade faturasi varsa veya WHMCS `upgradeinprogress` bildirirse `409 upgrade_conflict`. **Idempotency-Key:** ZORUNLU (POST — 24h replay korumasi). **Rate limit:** `services_changes` bucket (120 rpm). **Body:** `config_options` = `{ "": }` (bos olamaz, int=>int). `quote_only` opsiyonel bool (default false). Parametreler: - id (path, integer, zorunlu) [min 1] - Idempotency-Key (header, string(uuid), zorunlu) ZORUNLU — 24h replay korumasi. Istek govdesi (application/json, zorunlu): - config_options (object, zorunlu) WHMCS config option ID → secim/adet map'i (bos olamaz). - quote_only (boolean, opsiyonel) [varsayilan: false] true ise sadece fiyat teklifi (calconly), upgrade uygulanmaz. Ornek: { "config_options": { "10": 4, "11": 2048 }, "quote_only": false } Yanitlar: 200 — Upgrade uygulandi veya teklif dondu (quote_only'e gore) Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (UpgradeServiceResult, opsiyonel) Ornek — quote: Sadece teklif (quote_only=true) { "success": true, "data": { "service_id": 12345, "quote": true, "currency": "TRY", "price": "TRY 45,00", "price_amount": 45, "days_until_renewal": 18, "config_options": { "10": 4, "11": 2048 }, "order_id": null, "invoice_id": null, "status": null, "payment_applied": false, "balance_after": null, "notice": null }, "meta": { "trace_id": "...", "timestamp": "..." } } Ornek — applied: Gercek upgrade (kredi uygulandi) { "success": true, "data": { "service_id": 12345, "quote": false, "currency": "TRY", "price": "TRY 45,00", "price_amount": 45, "days_until_renewal": 18, "config_options": { "10": 4, "11": 2048 }, "order_id": 9310, "invoice_id": 8410, "status": "pending_provision", "payment_applied": true, "balance_after": 155, "notice": null }, "meta": { "trace_id": "...", "timestamp": "..." } } 400 — Gecersiz upgrade istegi (invalid_upgrade_request) veya Idempotency-Key eksik Ornek: { "success": false, "error": { "code": "invalid_upgrade_request", "message": "config_options bos olamaz", "details": {} } } 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 403 — Yetki yetersiz — bayi baska bir bayinin service_id'sine erismeye calisti veya admin scope gerektiren endpoint icin yetersiz scope. Mesaj generic tutulur (ServiceOwnershipMiddleware sizinti onler). → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "forbidden", "message": "Bu kaynaga erisim yetkiniz yok." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Servis bulunamadi 409 — Downgrade yasak (downgrade_not_allowed) veya upgrade cakismasi (upgrade_conflict) Ornek — downgrade { "success": false, "error": { "code": "downgrade_not_allowed", "message": "Downgrade desteklenmiyor", "details": { "price": "TRY -12,00" } } } Ornek — conflict { "success": false, "error": { "code": "upgrade_conflict", "message": "Servis icin odenmemis onceki upgrade faturasi mevcut", "details": {} } } 422 — Kilitli config option grubu degistirilemez (location_locked) Ornek: { "success": false, "error": { "code": "location_locked", "message": "Bu config option grubu upgrade ile degistirilemez (lokasyon veya sabit grup)", "details": { "locked_group_ids": [ 7 ] } } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } 502 — WHMCS API upstream hatasi (whmcs_upstream_error) ============================================================================== [Webhooks] ============================================================================== Bayi webhook endpoint kayit yonetimi (Faz 3.W.3). Bayi, abone oldugu event'ler gerceklestiginde (orn: service.suspended, product.price_changed) kendi sunucusunda bir HTTP endpoint'ine imzali POST almak icin kayit yapar. ABONELIK PATTERN'LERI (`event_types`): - `*` → tum eventler (catch-all) - `prefix.*` → namespace wildcard (service.*, product.*) — tek segment - exact → tam event ismi (service.suspended) GECERLI EVENT'LER (V1): service.created, service.suspended, service.unsuspended, service.terminated, product.price_changed, product.stock_changed, product.disabled, credit.added. Bilinmeyen event tipi 422 doner. `service.created` (Faz 3.W.4): siparis edilen VPS provisioning'i Rabisu tarafinda tamamlanip aktif olunca gonderilir (WHMCS domainstatus 'Pending' → 'Active'; cron polling tespiti). Bayi bu event ile servisin hazir oldugunu ANINDA ogrenir (GET /orders/{id} polling fallback olarak kalir). Payload: {provision_id, service_id, order_id, product_id, vps_id, status='active', occurred_at, schema_version}. `provision_id` = POST /orders yanitindaki `id` (bayi korelasyon anahtari). `credit.added` (Faz 3.B): bayiye kredi yuklendiginde (admin manuel kredi veya fatura iadesi) gonderilir. Payload: {reseller_id, amount, currency, reason (manual_credit|refund), description, related_invoice_id, new_balance, occurred_at}. Bu event SADECE ilgili bayiye gider (broadcast degil). Kaynak: WHMCS tblcredit polling (bin/credit-event-emitter.php). NOT: apply_credit (siparis odemesi) bu event'i URETMEZ — o GET /auth/balance/movements audit'ine OrderService tarafindan real-time yazilir. SECRET: POST yanitindaki `secret` plain HMAC secret YALNIZCA bir kez gosterilir. Kaybedilirse endpoint silinip yeniden olusturulmalidir. IMZA: Dispatcher her teslimatta iki header ekler: `X-Rabisu-Signature-V2: t=,v1=` — HMAC-SHA256(secret, "{t}.{X-Rabisu-Event-Type}.{ham govde}"). Alici |now - t| <= 300 sn ve imzali olay tipini dogrulamalidir (replay + tip degistirme korumasi). `X-Rabisu-Signature: sha256=` — yalniz govde; eski alicilar icin, replay korumasi YOK, yeni entegrasyonlarda kullanmayin. CIRCUIT BREAKER: 5 ardisik basarisiz teslimat endpoint'i otomatik pasif yapar (is_active=0). Bayi `PATCH {is_active: true}` ile reaktive eder. ------------------------------------------------------------------------------ GET /webhooks Bayinin webhook kayitlarini listele ------------------------------------------------------------------------------ operationId: listWebhooks Kimlik dogrulama: BasicAuth Bayi'ye (api_key) ait TUM endpoint'leri doner — pasif (is_active=0, circuit breaker ile kapanmis) olanlar dahil. Boylece bayi pasif endpoint'i gorup PATCH ile reaktive edebilir. Yanitlar: 200 — Webhook listesi Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (object, opsiyonel) - items (array, zorunlu) 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } ------------------------------------------------------------------------------ POST /webhooks Yeni webhook endpoint kaydet ------------------------------------------------------------------------------ operationId: createWebhook Kimlik dogrulama: BasicAuth Bayi'nin kendi sunucusundaki bir HTTP endpoint'ini, secilen event'lere abone ederek kaydeder. Yanitta donen `secret` plain HMAC secret YALNIZCA bir kez gosterilir. Hedef URL stricti: sadece `https`, kullanici bilgisi (user:pass@) yasak, ozel/dahili IP'ler reddedilir (SSRF korumasi). Istek govdesi (application/json, zorunlu): → bkz. sema: CreateWebhookRequest Ornek: { "target_url": "https://hooks.bayi.com/rabisu", "event_types": [ "service.suspended", "product.*" ], "description": "Uretim webhook" } Yanitlar: 201 — Endpoint olusturuldu (secret tek sefer doner) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (WebhookCreateResult, opsiyonel) 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 409 — Aktif webhook limiti asildi (webhook_limit_exceeded) → bkz. sema: ErrorResponse 422 — Validasyon hatasi (gecersiz URL / bilinmeyen event) → bkz. sema: ErrorResponse 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } ------------------------------------------------------------------------------ GET /webhooks/{id} Tek webhook detayi ------------------------------------------------------------------------------ operationId: getWebhook Kimlik dogrulama: BasicAuth Yanitlar: 200 — Webhook detayi Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (WebhookEndpoint, opsiyonel) 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Kayit yok veya baska bayinin (webhook_not_found) → bkz. sema: ErrorResponse ------------------------------------------------------------------------------ PATCH /webhooks/{id} Webhook kismi guncelle (reaktive / event_types / aciklama) ------------------------------------------------------------------------------ operationId: updateWebhook Kimlik dogrulama: BasicAuth Gonderilmeyen alan degismez (partial update). `is_active: true` gonderildiginde endpoint reaktive edilir ve `consecutive_failures` sifirlanir (circuit breaker temiz baslangic). `event_types` gonderilirse abonelik listesi TAMAMEN degistirilir (merge degil, replace). Istek govdesi (application/json, zorunlu): → bkz. sema: UpdateWebhookRequest Ornek: { "is_active": true, "event_types": [ "product.price_changed", "product.stock_changed" ] } Yanitlar: 200 — Guncellenmis webhook Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (WebhookEndpoint, opsiyonel) 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Kayit yok veya baska bayinin (webhook_not_found) → bkz. sema: ErrorResponse 422 — Validasyon hatasi (bos body / bilinmeyen event) → bkz. sema: ErrorResponse ------------------------------------------------------------------------------ DELETE /webhooks/{id} Webhook sil (soft delete) ------------------------------------------------------------------------------ operationId: deleteWebhook Kimlik dogrulama: BasicAuth Soft delete — kayit silinmez, `is_active=0` yapilir (audit + circuit breaker gecmisi korunur). Dispatcher bir daha bu endpoint'e teslimat yapmaz. Yanitlar: 200 — Silindi (soft) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (object, opsiyonel) - id (integer, zorunlu) [Ornek: 12] - deleted (boolean, zorunlu) [Ornek: true] 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Kayit yok veya baska bayinin (webhook_not_found) → bkz. sema: ErrorResponse ============================================================================== [ModuleReleases] ============================================================================== Bayi WHMCS modulu self-update. Modul en yeni surumu `GET .../latest` ile sorar, `download_path` ile imzali zip'i indirir. Zip'i kurmadan once `sha256` ve `signature` (zip baytlarinin ed25519 detached imzasi, base64) modulun icindeki Rabisu public key ile dogrulanmalidir. Surum dosyalari sunucuya elle konur; API yalniz okur. ------------------------------------------------------------------------------ GET /module-releases/{module}/latest Modulun en yeni gecerli surumu ------------------------------------------------------------------------------ operationId: getLatestModuleRelease Kimlik dogrulama: BasicAuth `MODULE_RELEASES_DIR/{module}/` altindaki gecerli manifestler arasinda `version_compare` ile en yuksek surumu doner (0.6.10 > 0.6.9). Gecersiz manifest (bozuk JSON, surum/modul uyusmazligi, guvensiz dosya adi, eksik zip, boyut/sha256/imza hatasi) atlanir. Gecerli surum yoksa 404 `release_not_found`. Default rate limit kovasi (`default`, 60/dk). Parametreler: - module (path, string, zorunlu) [pattern ^[a-z0-9-]{1,40}$] Modul kimligi (dizin adi), orn. `bayi-whmcs` Yanitlar: 200 — En yeni surum ozeti Yanit basliklari: X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (ModuleRelease, opsiyonel) Ornek: { "success": true, "data": { "module": "bayi-whmcs", "version": "0.6.6", "released_at": "2026-10-04", "notes": "- Sunucunuz hazir e-postasi\n- Webhook secret gizli alan", "size": 123456, "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "signature": "x0Jb3F...base64 (64 bayt ed25519 imza)...Ag==", "download_path": "/api/v1/module-releases/bayi-whmcs/0.6.6/download" }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-10-04T12:00:00Z" } } 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Gecerli surum yok (`release_not_found`) — modul dizini yok, manifest yok veya manifest gecersiz. Sebep public yanitta ayrilmaz. → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "release_not_found", "message": "Modul surumu bulunamadi.", "details": { "module": "bayi-whmcs", "version": null } }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-10-04T12:00:00Z" } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } ------------------------------------------------------------------------------ GET /module-releases/{module}/{version}/download Imzali modul zip'ini indir ------------------------------------------------------------------------------ operationId: downloadModuleRelease Kimlik dogrulama: BasicAuth Manifestteki zip'i binary stream olarak doner (JSON zarfi YOK). Istemci govdenin SHA-256 ozetini `X-Content-SHA256` / `latest.sha256` ile ve ed25519 imzasini `latest.signature` ile dogrulamadan kurulum yapmamalidir. Surum yok veya manifest gecersiz → 404 `release_not_found` (JSON). Default rate limit kovasi (`default`, 60/dk). Parametreler: - module (path, string, zorunlu) [pattern ^[a-z0-9-]{1,40}$] Modul kimligi (dizin adi), orn. `bayi-whmcs` - version (path, string, zorunlu) [pattern ^[0-9]+\.[0-9]+\.[0-9]+$] Surum (`MAJOR.MINOR.PATCH`) Yanitlar: 200 — Zip dosyasi Yanit basliklari: Content-Length: Zip boyutu (bayt) | Content-Disposition: Indirme dosya adi | X-Content-SHA256: Zip baytlarinin SHA-256 ozeti (kucuk harf hex) | Cache-Control | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining: Mevcut window'da kalan istek sayisi | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) (application/zip) string(binary) 401 — Kimlik dogrulama basarisiz — eksik header, hatali credential, IP whitelist disi veya revoke edilmis key. Detay sizdirilmaz; tum durumlar tek mesaj. Yanit basliklari: WWW-Authenticate: RFC 7617 Basic Auth challenge → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "unauthorized", "message": "Kimlik dogrulama basarisiz." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-25T01:00:00Z" } } 404 — Gecerli surum yok (`release_not_found`) — modul dizini yok, manifest yok veya manifest gecersiz. Sebep public yanitta ayrilmaz. → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "release_not_found", "message": "Modul surumu bulunamadi.", "details": { "module": "bayi-whmcs", "version": null } }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-10-04T12:00:00Z" } } 429 — Dakika basina limit asildi Yanit basliklari: Retry-After: 429 sonrasi tekrar denemeden once beklenmesi gereken saniye (RFC 7231 §7.1.3) | X-RateLimit-Limit: Bu key icin tanimli dakika basi limit | X-RateLimit-Remaining | X-RateLimit-Reset: Sonraki window basinin Unix epoch'u (UTC saniye) → bkz. sema: ErrorResponse Ornek: { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Dakika basina maksimum 60 istek limiti asildi. Lutfen 12 saniye sonra tekrar deneyin." }, "meta": { "trace_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-04-24T12:00:00Z" } } ============================================================================== [Admin] ============================================================================== Server-to-server admin kanali (`X-Admin-Token`, BasicAuth DEGIL). Rabisu WHMCS master modulu, bir musteri "Bayi ol" dediginde bu endpoint'lerle o musteri icin API key uretir/listeler. Bayi API key'leri ile erisilemez. ------------------------------------------------------------------------------ GET /admin/keys Bir bayinin aktif API key'lerini listele (secret YOK) ------------------------------------------------------------------------------ operationId: adminListKeys Kimlik dogrulama: AdminToken Belirtilen `reseller_id` icin aktif (is_active=1) API key'leri doner. Secret hash veya plaintext ASLA donmez — yalnizca metadata. Parametreler: - reseller_id (query, integer, zorunlu) WHMCS `tblclients.id`. Yanitlar: 200 — Aktif key listesi Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (object, opsiyonel) - reseller_id (integer, zorunlu) [Ornek: 42] - count (integer, zorunlu) [Ornek: 1] - keys (array, zorunlu) - id (integer, opsiyonel) [Ornek: 7] - reseller_id (integer, opsiyonel) [Ornek: 42] - name (string, opsiyonel) [Ornek: "Bayi paneli"] - api_key (string, opsiyonel) [Ornek: "rsk_live_5H2ABCD..."] - created_at (string, opsiyonel, null olabilir) - last_used_at (string, opsiyonel, null olabilir) - expires_at (string, opsiyonel, null olabilir) 401 — X-Admin-Token eksik veya hatali (unauthorized) → bkz. sema: ErrorResponse 403 — Admin API yapilandirilmamis (admin_disabled) → bkz. sema: ErrorResponse 422 — reseller_id eksik/gecersiz (invalid_input) → bkz. sema: ErrorResponse ------------------------------------------------------------------------------ POST /admin/keys Bayi icin yeni API key uret (secret TEK SEFER doner) ------------------------------------------------------------------------------ operationId: adminCreateKey Kimlik dogrulama: AdminToken Rabisu WHMCS master modulu bir musteri "Bayi ol" dediginde bu endpoint'i cagirip o musteri (reseller) icin API key + secret uretir. HTTP Basic Auth DEGIL — `X-Admin-Token` header'i ile korunur (server-to-server). **GUVENLIK:** - `permissions` icinde `admin` VERILEMEZ (422 forbidden) — bu kanal yalniz bayi izinli key uretir, sahiplik kontrolunu atlayan admin key uretemez. - `api_secret` (plaintext) yalnizca bu yanitta bir kez doner; DB'de yalnizca bcrypt hash tutulur. Kaybedilirse yeni key uretilmelidir. - `webhook_secret` (HMAC) da yalnizca bu yanitta bir kez doner. Bayi modulundeki Webhook Secret alanina yapistirilir. `POST /webhooks` bu secret'i yeniden uretmez. Istek govdesi (application/json, zorunlu): - reseller_id (integer, zorunlu) [Ornek: 42] WHMCS `tblclients.id` — key'in bagli oldugu bayi. - name (string, opsiyonel) [Ornek: "Bayi paneli"] Key etiketi (bos ise `Reseller - permissions (array, opsiyonel) Scope listesi. Varsayilan `["*"]`. `admin` yasak. - ip_whitelist (array, opsiyonel) Izinli IP listesi (bos/atlanirsa kisitlama yok). - rate_limit_rpm (integer, opsiyonel) [Ornek: 120] Per-key dakika limiti override (>0). Atlanirsa default. - expires_at (string, opsiyonel) [Ornek: "2027-01-01T00:00:00Z"] Key expire tarihi (RFC 3339 / MySQL datetime). Atlanirsa sonsuz. - auto_apply_credit (boolean, opsiyonel) [Ornek: true] POST /orders bakiye otomatik tahsilat toggle'i. Ornek: { "reseller_id": 42, "name": "Bayi paneli", "permissions": [ "*" ] } Yanitlar: 201 — Key uretildi (secret TEK SEFER) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (object, opsiyonel) - id (integer, zorunlu) [Ornek: 7] - reseller_id (integer, zorunlu) [Ornek: 42] - name (string, zorunlu) [Ornek: "Bayi paneli"] - api_key (string, zorunlu) [Ornek: "rsk_live_5H2ABCD..."] - api_secret (string, zorunlu) [Ornek: "rsks_live_9K3XYZ..."] Plaintext secret — SADECE burada doner. - webhook_secret (string, zorunlu) [Ornek: "d2ViaG9va19zZWNyZXQ"] Webhook HMAC secret — SADECE burada doner. POST /webhooks yeniden uretmez. - permissions (array, zorunlu) - ip_whitelist (array, opsiyonel, null olabilir) 401 — X-Admin-Token eksik veya hatali (unauthorized) → bkz. sema: ErrorResponse 403 — Admin API yapilandirilmamis (ADMIN_API_TOKEN bos → admin_disabled) → bkz. sema: ErrorResponse 422 — Gecersiz girdi — `reseller_id` eksik/gecersiz (invalid_input) veya `permissions` icinde `admin` (forbidden). → bkz. sema: ErrorResponse 500 — Key uretilemedi (internal_error) → bkz. sema: ErrorResponse ------------------------------------------------------------------------------ POST /admin/keys/{id}/webhook-secret Eski anahtara webhook secret ver (yoksa, tek sefer) ------------------------------------------------------------------------------ operationId: adminIssueWebhookSecret Kimlik dogrulama: AdminToken Bayi Ol oncesi acilmis anahtarda `webhook_secret_encrypted` bos ise HMAC secret uretir ve duz metni yalnizca bu yanitta doner. Kayit varsa `webhook_secret` null doner. Parametreler: - id (path, integer, zorunlu) Istek govdesi (application/json, zorunlu): - reseller_id (integer, zorunlu) Yanitlar: 200 — Uretildi veya zaten vardi Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse 404 — Key bulunamadi → bkz. sema: ErrorResponse ------------------------------------------------------------------------------ POST /admin/keys/{id}/rotate Bayi API key secret'ini yenile (secret TEK SEFER doner) ------------------------------------------------------------------------------ operationId: adminRotateKey Kimlik dogrulama: AdminToken `api_key` public kismi DEGISMEZ; yalniz secret yenilenir. Eski secret **DERHAL** gecersiz olur (immediate rotate — 24s grace / dual-secret v2 backlog). APCu auth cache invalidate edilir. Body'deki `reseller_id` sahiplik guard'idir — baska bayinin key'i 404 `key_not_found` doner (izolasyon, sizinti yok). Parametreler: - id (path, integer, zorunlu) mod_reseller_api_keys.id Istek govdesi (application/json, zorunlu): - reseller_id (integer, zorunlu) [Ornek: 42] Key'in bagli oldugu bayi (WHMCS tblclients.id). Ornek: { "reseller_id": 42 } Yanitlar: 200 — Secret yenilendi (plaintext TEK SEFER) Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (object, opsiyonel) - id (integer, zorunlu) [Ornek: 7] - reseller_id (integer, zorunlu) [Ornek: 42] - name (string, zorunlu) [Ornek: "Bayi paneli"] - api_key (string, zorunlu) [Ornek: "rsk_live_5H2ABCD..."] - api_secret (string, zorunlu) [Ornek: "rsks_live_9K3XYZ..."] Yeni plaintext secret — SADECE burada doner. 401 — X-Admin-Token eksik veya hatali (unauthorized) → bkz. sema: ErrorResponse 403 — Admin API yapilandirilmamis (admin_disabled) → bkz. sema: ErrorResponse 404 — Key yok, revoked/expired veya baska bayinin (key_not_found) → bkz. sema: ErrorResponse 422 — reseller_id eksik/gecersiz (invalid_input) → bkz. sema: ErrorResponse ------------------------------------------------------------------------------ POST /admin/keys/{id}/revoke Bayi API key'ini iptal et (soft delete) ------------------------------------------------------------------------------ operationId: adminRevokeKey Kimlik dogrulama: AdminToken Key'i derhal deaktif eder (`revoked_at` set) + APCu auth cache invalidate. Kayit audit icin korunur. Body'deki `reseller_id` sahiplik guard'idir — baska bayinin key'i 404 `key_not_found`. Parametreler: - id (path, integer, zorunlu) mod_reseller_api_keys.id Istek govdesi (application/json, zorunlu): - reseller_id (integer, zorunlu) [Ornek: 42] Key'in bagli oldugu bayi (WHMCS tblclients.id). Ornek: { "reseller_id": 42 } Yanitlar: 200 — Key iptal edildi Asagidakilerin birlesimi (allOf): → bkz. sema: ApiResponse - data (object, opsiyonel) - id (integer, zorunlu) [Ornek: 7] - revoked (boolean, zorunlu) [Ornek: true] 401 — X-Admin-Token eksik veya hatali (unauthorized) → bkz. sema: ErrorResponse 403 — Admin API yapilandirilmamis (admin_disabled) → bkz. sema: ErrorResponse 404 — Key yok, revoked/expired veya baska bayinin (key_not_found) → bkz. sema: ErrorResponse 422 — reseller_id eksik/gecersiz (invalid_input) → bkz. sema: ErrorResponse ============================================================================== BOLUM 3 — VERI SEMALARI (components/schemas) ============================================================================== ------------------------------------------------------------------------------ ServiceLifecycleStatus (string) ------------------------------------------------------------------------------ Hizmet yasam dongusu durumu — API'nin kendi degerleri, kaynak sistemden bagimsiz. Sunucunun acik/kapali (power) durumu DEGILDIR. string [degerler: "pending" | "active" | "suspended" | "terminated" | "cancelled" | "fraud" | "unknown"; Ornek: "active"] ------------------------------------------------------------------------------ ServiceListItem (object) ------------------------------------------------------------------------------ - service_id (integer, zorunlu) [Ornek: 7777] - product_id (integer, zorunlu) [Ornek: 101] - product_name (string, opsiyonel) [Ornek: "VPS X4"] - status (ServiceLifecycleStatus, zorunlu) - domain (string, zorunlu) [Ornek: "web01.bayi.example"] - dedicated_ip (string, zorunlu) [Ornek: "203.0.113.42"] - billing_cycle (string, zorunlu) [Ornek: "Monthly"] - first_payment_amount (number(float), opsiyonel) [Ornek: 120] Ilk odeme (bayinin alis fiyati, `currency` biriminde) - recurring_amount (number(float), opsiyonel) [Ornek: 99] Yinelenen tutar (bayinin alis fiyati, `currency` biriminde) - discount (allOf, opsiyonel, null olabilir) Bayinin indirim grubu hizmetin urununu kapsiyorsa grup adi + oran; aksi halde null. Asagidakilerin birlesimi (allOf): → bkz. sema: ResellerDiscount - discounted_recurring_amount (number(float), opsiyonel, null olabilir) [Ornek: 79.2] Bir sonraki yenileme faturasinda odenecek tutar (`recurring_amount` indirimli, kurusa yuvarlanmis). Kapsam disinda null. - next_due_date (string(date), zorunlu, null olabilir) [Ornek: "2026-11-24"] - reg_date (string(date), zorunlu, null olabilir) [Ornek: "2026-04-24"] ------------------------------------------------------------------------------ ServiceListResponse (object) ------------------------------------------------------------------------------ Imlec modunda `next_after` doner; sayfa modunda (`page`/`q`) yerine `total`, `page`, `per_page`, `total_pages` doner. - items (array, zorunlu) - currency (string, zorunlu, null olabilir) [Ornek: "TRY"] Tutarlarin para birimi (bayinin Rabisu hesap para birimi) - next_after (integer, opsiyonel, null olabilir) [Ornek: 7777] (Imlec modu) Sonraki sayfa icin `after` degeri; null = liste bitti - total (integer, opsiyonel) [Ornek: 1240] (Sayfa modu) Filtreye uyan toplam hizmet - page (integer, opsiyonel) [Ornek: 1] (Sayfa modu) Donen sayfa - per_page (integer, opsiyonel) [Ornek: 50] (Sayfa modu) Sayfa basina oge - total_pages (integer, opsiyonel) [Ornek: 25] (Sayfa modu) Toplam sayfa ------------------------------------------------------------------------------ CreateWebhookRequest (object) ------------------------------------------------------------------------------ - target_url (string(uri), zorunlu) [Ornek: "https://hooks.bayi.com/rabisu"] HTTPS endpoint (zorunlu https). Ozel/dahili IP'ler reddedilir (SSRF). - event_types (array, zorunlu) Abonelik pattern'leri: `*`, `prefix.*` (service.*, product.*, credit.*) veya tam event ismi (orn: credit.added). Bilinmeyen tip 422 dondurur. - description (string, opsiyonel, null olabilir) [Ornek: "Uretim webhook"] Serbest aciklama (opsiyonel) ------------------------------------------------------------------------------ UpdateWebhookRequest (object) ------------------------------------------------------------------------------ Partial update — gonderilmeyen alan degismez. En az bir alan gonderilmeli (aksi halde 422). - is_active (boolean, opsiyonel) [Ornek: true] `true` → reaktive (consecutive_failures sifirlanir). `false` → pasiflestir (DELETE ile ayni etki). - event_types (array, opsiyonel) Abonelik listesini TAMAMEN degistirir (replace) - description (string, opsiyonel, null olabilir) ------------------------------------------------------------------------------ WebhookEndpoint (object) ------------------------------------------------------------------------------ - id (integer, zorunlu) [Ornek: 12] - target_url (string(uri), zorunlu) [Ornek: "https://hooks.bayi.com/rabisu"] - event_types (array, zorunlu) - description (string, opsiyonel, null olabilir) [Ornek: "Uretim webhook"] - is_active (boolean, zorunlu) [Ornek: true] - consecutive_failures (integer, zorunlu) [Ornek: 0] Ardisik basarisiz teslimat sayaci (5'te otomatik pasif) - last_delivery_at (string(date-time), opsiyonel, null olabilir) [Ornek: "2026-05-29T12:00:00Z"] - last_failure_at (string(date-time), opsiyonel, null olabilir) - created_at (string(date-time), zorunlu) [Ornek: "2026-05-29T10:00:00Z"] - updated_at (string(date-time), zorunlu) [Ornek: "2026-05-29T10:00:00Z"] ------------------------------------------------------------------------------ WebhookCreateResult (object) ------------------------------------------------------------------------------ - endpoint (WebhookEndpoint, zorunlu) - secret (string, opsiyonel) [Ornek: "Zm9vYmFyYmF6cXV4..."] Eski anahtarda (Bayi Ol webhook secret'i yok) plain HMAC secret — yalnizca bu yanitta doner. Bayi Ol sonrasi uretilen anahtarda alan yoktur; secret orada bir kez verilir. - secret_notice (string, zorunlu) [Ornek: "Bu secret yalnizca bir kez gosterilir, guvenli sakla."] ------------------------------------------------------------------------------ ModuleRelease (object) ------------------------------------------------------------------------------ - module (string, zorunlu) [Ornek: "bayi-whmcs"] - version (string, zorunlu) [pattern ^[0-9]+\.[0-9]+\.[0-9]+$; Ornek: "0.6.6"] - released_at (string, zorunlu, null olabilir) [Ornek: "2026-10-04"] Manifestteki yayin tarihi (serbest metin, genelde YYYY-MM-DD) - notes (string, zorunlu) [Ornek: "- Sunucunuz hazir e-postasi\n- Webhook secret gizli alan"] Surum notlari (satirlar `\n` ile ayrilir); manifestte yoksa bos - size (integer, zorunlu) [Ornek: 123456] Zip boyutu (bayt) - sha256 (string, zorunlu) [pattern ^[0-9a-f]{64}$] Zip baytlarinin SHA-256 ozeti (kucuk harf hex) - signature (string, zorunlu) Zip baytlarinin ed25519 detached imzasi (base64) - download_path (string, zorunlu) [Ornek: "/api/v1/module-releases/bayi-whmcs/0.6.6/download"] Indirme yolu (API host'una gore) ------------------------------------------------------------------------------ ApiResponse (object) ------------------------------------------------------------------------------ - success (boolean, zorunlu) [degerler: true] - data (object, zorunlu) Endpoint'e ozgu payload (alt schema'lar override eder) - meta (Meta, zorunlu) ------------------------------------------------------------------------------ ErrorResponse (object) ------------------------------------------------------------------------------ - success (boolean, zorunlu) [degerler: false] - error (object, zorunlu) - code (string, zorunlu) [Ornek: "invalid_credentials"] Snake_case slug (rate_limit_exceeded, invalid_credentials, vb.) - message (string, zorunlu) [Ornek: "Gecersiz kimlik bilgileri."] Insan okunur mesaj (Turkce, son kullaniciya gosterilebilir) - details (object, opsiyonel) Opsiyonel — endpoint'e ozgu ek alanlar (validation hatalari, retry_after, vb.) - meta (Meta, zorunlu) ------------------------------------------------------------------------------ Meta (object) ------------------------------------------------------------------------------ - trace_id (string(uuid), zorunlu) [Ornek: "550e8400-e29b-41d4-a716-446655440000"] Request-scoped UUIDv4 — `X-Request-ID` header'i olarak da response'da doner. `TraceIdMiddleware` set eder; tum log satirlarinda korelasyon icin kullanilir. - timestamp (string(date-time), zorunlu) [Ornek: "2026-04-24T12:00:00Z"] Response uretim zamani (UTC, ISO 8601) ------------------------------------------------------------------------------ ProductItem (object) ------------------------------------------------------------------------------ - id (integer, zorunlu) [Ornek: 101] - service_type (string, zorunlu) [degerler: "vps" | "dedicated" | "hosting"; Ornek: "vps"] Hizmet tipi (Rabisu Master). Bayi musteri ekranini buna gore secer. - management (string, zorunlu) [degerler: "full" | "basic"; Ornek: "full"] `full` = API bu urunun panelini yonetir; `basic` = yalniz erisim bilgileri (credentials) verilir, panel islemleri 409 `management_unavailable` doner. - name (string, zorunlu) [Ornek: "VPS Basic"] - group_id (integer, zorunlu) [Ornek: 1] WHMCS `tblproducts.gid` - group_name (string, zorunlu) [Ornek: "VPS Sunucular"] WHMCS `tblproductgroups.name`. Grup bulunamazsa bos string. - description (string, zorunlu) [Ornek: "1 CPU / 2 GB RAM / 40 GB SSD"] - pricing (ProductPricing, zorunlu) - discount (allOf, zorunlu, null olabilir) Bayinin indirim grubu bu urunu kapsiyorsa grup adi + oran; aksi halde null. `pricing` liste fiyati olarak kalir. Asagidakilerin birlesimi (allOf): → bkz. sema: ResellerDiscount - discounted_pricing (allOf, zorunlu, null olabilir) Bayinin odeyecegi (indirimli) fiyat; `pricing` ile ayni yapi, her tutar `round(liste * (1 - percent/100), 2)`. Kapsam disinda null. Rabisu faturasinda urun satiri (yapilandirilabilir secenekler dahil) bu oranla kesilir; satir bazli yuvarlama nedeniyle 0.01 fark olabilir. Asagidakilerin birlesimi (allOf): → bkz. sema: ProductPricing - stock_active (boolean, zorunlu) [Ornek: true] Urunun su anda siparise ACIK olup olmadigi. False ise UI'de "Tukendi / bakimda" olarak gosterilebilir. Siparis verme asamasinda StockService re-check yapar. Not: `stock_available` ile ayni degeri tasir (legacy alias). - stock_available (boolean, zorunlu) [Ornek: true] Bayi modulu (Faz 3) cron'unun `tblproducts.stockcontrol` senkronunda kullandigi alan. Su an `stock_active` ile ozdestir; ileride `quantity_left` tukenince false olacak. - quantity_left (integer, zorunlu, null olabilir) Kalan stok adedi. Faz 1'de her zaman NULL (sinirsiz/unlimited). Faz 3'te bayi modulu bunu WHMCS `tblproducts.qty` ile senkron edip pozitif tam sayiya dusurecek. - stock_updated_at (string(date-time), zorunlu, null olabilir) [Ornek: "2026-04-24T11:30:05+00:00"] `/ozl/active.json` dosyasinin son degistirilme zamani (ISO 8601, UTC). Dosya yoksa NULL (fail-open — StockService sinirsiz kabul eder). Faz 3 bayi modulu bu alani `last_synced_at` olarak kullanarak incremental sync yapar. ------------------------------------------------------------------------------ ProductListResponse (object) ------------------------------------------------------------------------------ - stock (object, zorunlu) - global_active (boolean, zorunlu) [Ornek: true] - active_location_ids (array, zorunlu, null olabilir) NULL = lokasyon kisitlamasi yok (hepsi aktif) - updated_at (string(date-time), zorunlu, null olabilir) [Ornek: "2026-04-24T11:30:05+00:00"] `/ozl/active.json` son degistirilme zamani (ISO 8601 UTC). Her urunun `stock_updated_at` alani ile ayni degeri tasir. - products (array, zorunlu) ------------------------------------------------------------------------------ ProductPricingCycle (object) ------------------------------------------------------------------------------ Tek bir billing cycle icin fiyat. `-1` (cycle kapali) cycle'lar cikti'ya HIC DAHIL EDILMEZ (sadece sunulan cycle'lar listelenir). - recurring (number(float), zorunlu) [Ornek: 10] Donemsel ucret (currency birimi `currency_code`). - setup (number(float), zorunlu) [Ornek: 0] Ilgili cycle icin kurulum ucreti (yoksa 0). ------------------------------------------------------------------------------ ProductPricingCurrency (object) ------------------------------------------------------------------------------ - currency_code (string, zorunlu) [Ornek: "USD"] - prefix (string, zorunlu) [Ornek: "$"] - suffix (string, zorunlu) [Ornek: ""] - cycles (object, zorunlu) cycle adi -> fiyat (monthly|quarterly|semiannually|annually|biennially|triennially) ------------------------------------------------------------------------------ ProductPricing (object) ------------------------------------------------------------------------------ Para birimi koduna gore fiyat haritasi (CURRENCY-AGNOSTIK): WHMCS `tblcurrencies`'te hangi birimler tanimliysa hepsi doner. WHMCS multi-currency native — doviz donusumu YAPILMAZ; her birimin fiyati bagimsiz tutulur. Rabisu yakinda USD-only olunca bu harita tek currency'ye daralir (sozlesme degismez). Ornek: { "USD": { "currency_code": "USD", "prefix": "$", "suffix": "", "cycles": { "monthly": { "recurring": 10, "setup": 0 } } }, "TRY": { "currency_code": "TRY", "prefix": "", "suffix": " TL", "cycles": { "monthly": { "recurring": 400, "setup": 0 } } } } ------------------------------------------------------------------------------ ResellerDiscount (object) ------------------------------------------------------------------------------ Bayinin indirim grubu (Rabisu Master modulu 1.4.0, `mod_rabisu_master_tiers`). Liste fiyatina ek indirimdir; Rabisu faturasinda kapsamdaki urun satiri (yapilandirilabilir secenekler dahil) bu oranla indirimli kesilir: yeni siparis, yenileme ve yukseltme. - tier (string, zorunlu) [Ornek: "Altin"] Grup adi. - percent (number(float), zorunlu) [Ornek: 20] Indirim yuzdesi (0 < percent < 100). ------------------------------------------------------------------------------ ConfigSubOption (object) ------------------------------------------------------------------------------ - id (integer, zorunlu) [Ornek: 701] WHMCS `tblproductconfigoptionssub.id` - name (string, zorunlu) [Ornek: "Frankfurt"] - stock_active (boolean, zorunlu) [Ornek: false] Bu alt-secenegin (orn. tekil lokasyon / OS surumu) bayilere satista STOKTA olup olmadigi — Rabisu Master modulu (`mod_rabisu_master_configoption_stock`). `false` ise bayi modulu bu secenegi katalogda gizler ve sipariste secilirse 409 `config_option_out_of_stock` doner. Kaydi olmayan alt-secenek / master modul kurulu degilse `true` (guvenli varsayilan). WHMCS `hidden` bayragindan BAGIMSIZ — yalniz bayi satisini kapatir. - pricing (ProductPricing, zorunlu) - discounted_pricing (allOf, zorunlu, null olabilir) Urunun `discount`'u varsa alt-secenegin indirimli fiyati (ayni oran); aksi halde null. Asagidakilerin birlesimi (allOf): → bkz. sema: ProductPricing ------------------------------------------------------------------------------ ConfigOption (object) ------------------------------------------------------------------------------ Configurable option grubu + secenekleri. LOKASYON da Virtualizor'da bir configurable option olarak modellenir; bu yuzden lokasyon bazli fiyat farki dogal olarak bu agacin icinde doner (ozel alan YOK). - group_id (integer, zorunlu) [Ornek: 7] WHMCS `tblproductconfiggroups.id` - group_name (string, zorunlu) [Ornek: "Lokasyon"] - role (string, zorunlu) [degerler: "resource" | "fixed"; Ornek: "fixed"] Grup rolu (2 rol) — Rabisu Master WHMCS modulunden gelir (`mod_rabisu_master_configgroup_meta`). `resource` = upgrade'de yukseltilebilir (RAM/CPU/disk), `fixed` = kilitli/sabit (LOKASYON DAHIL, upgrade'de degismez). Kaydi olmayan grup / master modul kurulu degilse `fixed` (guvenli varsayilan). Eski `location` degeri de `fixed` (kilitli) olarak doner. Bayi elle isaretlemez. - id (integer, zorunlu) [Ornek: 70] WHMCS `tblproductconfigoptions.id` - name (string, zorunlu) [Ornek: "Sunucu Lokasyonu"] - type (integer, zorunlu) [Ornek: 1] WHMCS `optiontype` (1=dropdown, 2=radio, 3=yesno, 4=quantity) - type_label (string, zorunlu) [degerler: "dropdown" | "radio" | "yesno" | "quantity" | "unknown"; Ornek: "dropdown"] - qty_min (integer, zorunlu) [Ornek: 0] - qty_max (integer, zorunlu) [Ornek: 0] - options (array, zorunlu) ------------------------------------------------------------------------------ ProductDetail (allOf) ------------------------------------------------------------------------------ `GET /products/{id}` yaniti — `ProductItem` alanlari + `config_options` (tam configurable agaci, her alt secenek icin per-currency fiyat). Liste endpoint'i hafif kalmasi icin `config_options` SADECE detayda doner. Asagidakilerin birlesimi (allOf): → bkz. sema: ProductItem - config_options (array, zorunlu) ------------------------------------------------------------------------------ CreateOrderRequest (object) ------------------------------------------------------------------------------ - product_id (integer, zorunlu) [min 1; Ornek: 101] WHMCS `tblproducts.id` - period (string, zorunlu) [degerler: "monthly" | "quarterly" | "semiannually" | "annually" | "biennially" | "triennially"; Ornek: "monthly"] Abonelik donemi — WHMCS `tblhosting.billingcycle` degerine karsilik gelir. - extra (object, opsiyonel) WHMCS `AddOrder` action'ina passthrough alanlari (hostname, configoption1..N, customfield, rootpw vb.). Sadece scalar/null degerler kabul edilir; nested object YOK. ------------------------------------------------------------------------------ CreateOrderResponse (object) ------------------------------------------------------------------------------ - id (integer, zorunlu) [Ornek: 77] `mod_reseller_provisions.id` — GET /orders/{id} bu id'yi kullanir - order_id (integer, zorunlu) [Ornek: 9001] WHMCS `tblorders.id` - invoice_id (integer, zorunlu) [Ornek: 8001] WHMCS `tblinvoices.id` - invoice_total (number(float), zorunlu, null olabilir) [Ornek: 99] Fatura toplam tutari. WHMCS bazi kurulumlarda AddOrder response'unda `orderamount`/`invoiceamount` alanlarini dondurmez — bu durumda null gelir. Bayi modulu null durumunu `GetInvoice` ile sorgulayabilir (opsiyonel). - currency (string, zorunlu) [Ornek: "TRY"] Bayinin WHMCS hesap para birimi (tblclients.currency); okunamazsa `BALANCE_DEFAULT_CURRENCY`. - status (string, zorunlu) [degerler: "pending_provision" | "pending_payment" | "payment_failed"; Ornek: "pending_provision"] `ApplyCredit` akisinin 3 sonucundan biri: - `pending_provision` — bakiye yeterli, WHMCS hook queue cron'u tetikledi; VPS kuruluyor. - `pending_payment` — bakiye yetersiz; fatura WHMCS'te unpaid, bayi manuel odeyebilir. - `payment_failed` — ApplyCredit 5xx/timeout; admin mudahale edecek (alarm log). - payment_applied (boolean, zorunlu) [Ornek: true] ApplyCredit basarili ise true (fatura PAID) - balance_after (number(float), zorunlu, null olabilir) [Ornek: 26] ApplyCredit sonrasi bayi bakiyesi; basarisizsa NULL - notice (string, zorunlu, null olabilir) [degerler: "insufficient_credit" | "auto_apply_credit_disabled" | null] Bayiye gorunur bilgi notu. - `insufficient_credit`: Bakiye yetersiz, ApplyCredit denendi ama reddedildi. Fatura WHMCS'te unpaid duruyor. - `auto_apply_credit_disabled`: Bayinin `auto_apply_credit` toggle'i KAPALI oldugu icin ApplyCredit cagrisi HIC yapilmadi. Bayi faturayi manuel odeyecek. Toggle admin CLI ile acilir: `php bin/api-key-update.php --auto-apply-credit=1`. - `null`: Bildirilecek bir durum yok (mesela `pending_provision`). - due_date (string(date), zorunlu, null olabilir) Fatura son odeme tarihi (WHMCS `tblinvoices.duedate`). Faz 1'de WHMCS response'u dondurmezse NULL; Faz 2'de WHMCS GetInvoice cagrisi ile doldurulacak. ------------------------------------------------------------------------------ OrderItem (object) ------------------------------------------------------------------------------ - id (integer, zorunlu) [Ornek: 77] - order_id (integer, zorunlu, null olabilir) [Ornek: 9001] - service_id (integer, zorunlu, null olabilir) [Ornek: 7001] WHMCS `tblhosting.id` — WHMCS hook yaratti ise dolu - invoice_id (integer, zorunlu, null olabilir) [Ornek: 8001] - product_id (integer, zorunlu) [Ornek: 101] - status (string, zorunlu) [degerler: "pending_payment" | "pending_provision" | "active" | "suspended" | "terminated" | "payment_failed" | "provision_failed" | "pending" | "unknown_state"; Ornek: "pending_provision"] - payment_applied (boolean, zorunlu) [Ornek: true] - notice (string, zorunlu, null olabilir) [degerler: "insufficient_credit" | "auto_apply_credit_disabled" | "order_unknown_state" | "reconciled" | "order_not_confirmed" | null] - created_at (string, zorunlu) [Ornek: "2026-04-24 12:00:00"] - updated_at (string, zorunlu) [Ornek: "2026-04-24 12:00:00"] ------------------------------------------------------------------------------ SettleOrderResult (object) ------------------------------------------------------------------------------ - id (integer, zorunlu) [Ornek: 1024] `mod_reseller_provisions.id` - order_id (integer, zorunlu) [Ornek: 9401] - invoice_id (integer, zorunlu) [Ornek: 8401] - currency (string, zorunlu) [Ornek: "TRY"] Bayinin WHMCS hesap para birimi (tblclients.currency); okunamazsa `BALANCE_DEFAULT_CURRENCY`. - status (string, zorunlu) [degerler: "pending_payment" | "pending_provision" | "payment_failed"; Ornek: "pending_provision"] - payment_applied (boolean, zorunlu) [Ornek: true] Bu cagri (veya onceki) krediyi uyguladi mi - balance_after (number(float), zorunlu, null olabilir) [Ornek: 10] Kredi bu cagrida uygulandiysa yeni bakiye; aksi null - notice (string, zorunlu, null olabilir) [degerler: "insufficient_credit" | "no_invoice" | null] - already_applied (boolean, zorunlu) [Ornek: false] true ise siparis zaten odenmisti, WHMCS'e dokunulmadi (idempotent no-op). ------------------------------------------------------------------------------ OrderListResponse (object) ------------------------------------------------------------------------------ - items (array, zorunlu) ------------------------------------------------------------------------------ BalanceResponse (object) ------------------------------------------------------------------------------ - reseller_id (integer, zorunlu) [Ornek: 1024] WHMCS `tblclients.id` - credit (number(float), zorunlu) [Ornek: 125.5] Mevcut kredi bakiyesi. WHMCS'de client bulunamazsa 0.0. - currency (string, zorunlu) [Ornek: "TRY"] Bayinin WHMCS hesap para birimi (tblclients.currency); okunamazsa `BALANCE_DEFAULT_CURRENCY`. - cached (boolean, zorunlu) [Ornek: false] APCu cache hit ise true (TTL bitene kadar ayni deger) ------------------------------------------------------------------------------ CreditMovement (object) ------------------------------------------------------------------------------ Tek bir bakiye hareketi (append-only audit kaydi). UPDATE/DELETE DB trigger ile yasaktir — duzeltme "compensating" satir ile yapilir. - id (integer(int64), zorunlu) [Ornek: 4821] Hareket kaydi ID (artan — tie-breaker siralama icin) - amount (number(float), zorunlu) [Ornek: -99] Pozitif = ekleme (kredi yuklendi), Negatif = cikis (krediden odeme) - currency (string, zorunlu) [Ornek: "TRY"] ISO 4217 (TRY, USD, EUR) - reason (string, zorunlu) [Ornek: "apply_credit"] Hareket nedeni. Bilinen degerler: `apply_credit` (siparis odemesi), `refund`, `manual_credit`, `webhook_credit_added`. - description (string, opsiyonel, null olabilir) [Ornek: "Siparis #5012 / fatura #8841 icin bakiyeden otomatik odeme"] Serbest aciklama (WHMCS admin notu veya sistem mesaji) - related_invoice_id (integer, opsiyonel, null olabilir) [Ornek: 8841] Iliskili WHMCS `tblinvoices.id` (varsa) - related_order_id (integer, opsiyonel, null olabilir) [Ornek: 5012] Iliskili WHMCS `tblorders.id` (varsa) - new_balance (number(float), zorunlu) [Ornek: 26.5] Hareket sonrasi bakiye snapshot'i - occurred_at (string(date-time), zorunlu) [Ornek: "2026-05-29T13:05:11Z"] Hareketin gercek zamani (WHMCS tarafi) - created_at (string(date-time), zorunlu) [Ornek: "2026-05-29T13:05:11Z"] API kaydinin yazildigi zaman ------------------------------------------------------------------------------ AuthMeResponse (object) ------------------------------------------------------------------------------ - api_key_id (integer(int64), zorunlu) [Ornek: 42] - reseller_id (integer, zorunlu) [Ornek: 1024] WHMCS `tblclients.id` - name (string, zorunlu) [Ornek: "Production Server"] Key etiketi (bayi tarafindan verilen) - permissions (array, zorunlu) - client_ip (string, zorunlu) [Ornek: "203.0.113.42"] Trusted proxy resolver ile cikarilan gercek istemci IP'si - last_used_at (string(date-time), opsiyonel, null olabilir) [Ornek: "2026-04-24T11:59:59Z"] Bu key'in son kullanim zamani (RFC 3339) - expires_at (string(date-time), opsiyonel, null olabilir) NULL = sonsuz - rate_limit_rpm (integer, opsiyonel, null olabilir) [Ornek: 100] Eski tek-sayi override; yalniz `default` kovayi etkiler. NULL = kova varsayilani - rate_limits (object, opsiyonel) Kova bazli ETKIN dakika limitleri (varsayilan + bayiye ozel) - auto_apply_credit (boolean, zorunlu) [Ornek: true] POST /orders akisinda WHMCS `ApplyCredit` cagrisinin otomatik yapilip yapilmayacagini belirler. - `true` (default): siparis olusturuldugunda bakiyeden TEK cagrida tahsilat denenir. Yeterli bakiye → `pending_provision`; yetersiz → `pending_payment` + `notice=insufficient_credit`; 5xx/timeout → `payment_failed`. - `false`: ApplyCredit hic cagrilmaz. Siparis her zaman `pending_payment` + `notice=auto_apply_credit_disabled` doner; bayi faturayi Rabisu panelinden veya banka havalesiyle manuel oder. Bayinin kendi muhasebe tercihlerine gore acip kapatabildigi bir bayragi. Toggle admin CLI ile degistirilir: `php bin/api-key-update.php --auto-apply-credit=0|1` Degisiklik sonrasi APCu auth cache otomatik invalidate edilir, bir sonraki request fresh degeri goruyor. ------------------------------------------------------------------------------ ServiceDetail (object) ------------------------------------------------------------------------------ WHMCS-side hizmet bilgisi (`tblhosting`) ile Virtualizor-side runtime ozeti (`act=listvs`) birlestirilmis payload. `vps_id=null` veya Virtualizor upstream hatasi durumunda `virt=null` ve `virt_unavailable=true` doner — graceful degradation. - service_id (integer, zorunlu) [Ornek: 7777] WHMCS `tblhosting.id` - product_id (integer, zorunlu) [Ornek: 101] WHMCS `tblhosting.packageid` - server_id (integer, zorunlu) [Ornek: 12] WHMCS `tblhosting.server` (Virtualizor master node id) - vps_id (integer, zorunlu, null olabilir) [Ornek: 1234] Virtualizor `vpsid` (numeric). Resmi Virtualizor WHMCS modulu tarafindan custom field'a (`tblcustomfieldsvalues`, alan adi `vpsid` veya `vpsid|...`) yazilir. Custom field tanimsiz veya value bos ise null doner — whitelabel paketler / henuz kurulmamis hizmetler. - domain (string, zorunlu) [Ornek: "web01.bayi.example"] - dedicated_ip (string, zorunlu) [Ornek: "203.0.113.42"] WHMCS'de tahsisli ana IP (Virtualizor `ips` ile farkli olabilir) - nameservers (array, zorunlu) ns1/ns2 birlesmis liste (bos string'ler ayiklanir) - billing_cycle (string, zorunlu) [Ornek: "monthly"] WHMCS billing cycle (monthly, quarterly, semiannually, annually, ...) - first_payment_amount (number(float), zorunlu) [Ornek: 99] - recurring_amount (number(float), zorunlu) [Ornek: 99] - discount (allOf, opsiyonel, null olabilir) Bayinin indirim grubu hizmetin urununu kapsiyorsa grup adi + oran; aksi halde null. Asagidakilerin birlesimi (allOf): → bkz. sema: ResellerDiscount - discounted_recurring_amount (number(float), opsiyonel, null olabilir) [Ornek: 79.2] Bir sonraki yenileme faturasinda odenecek tutar (`recurring_amount` indirimli, kurusa yuvarlanmis). Kapsam disinda null. - next_due_date (string(date), zorunlu, null olabilir) [Ornek: "2026-05-24"] WHMCS `nextduedate`. '0000-00-00' veya bos null doner. - domain_status (string, zorunlu) [degerler: "Pending" | "Active" | "Suspended" | "Terminated" | "Cancelled" | "Fraud" | "Completed"; Ornek: "Active"] Kaynak sistem (WHMCS) degeri — geriye uyumluluk icin. Yeni istemciler `status` kullanmali. - status (ServiceLifecycleStatus, opsiyonel) - reg_date (string(date), zorunlu, null olabilir) [Ornek: "2026-04-24"] - virt (object, zorunlu, null olabilir) Virtualizor `act=vs` runtime ozeti. vps_id null ise veya upstream hata varsa null. `running` field'i BURADA DEGIL — Virtualizor admin API `act=vs` response'unda runtime flag guvenilir donmedigi icin (BUG-008 live test 2026-05-19) anlik calisma durumu icin `GET /services/{id}/status` endpoint'ini kullanin. - hostname (string, zorunlu) [Ornek: "web01.bayi.example"] - os_name (string, opsiyonel) [Ornek: "almalinux-8-x86_64"] VPS isletim sistemi adi (listvs `os_name`). Alan yoksa bos. - default_user (string, opsiyonel) [Ornek: "root"] OS'a gore varsayilan yonetici kullanici adi — Windows ise "Administrator", diger her sey (Linux) icin "root". Bayi musteri panelinde root erisim kullanici adi olarak gosterilir (WHMCS e-postasi DEGIL). - cores (integer, opsiyonel) [Ornek: 2] vCPU cekirdek sayisi (listvs `cores`). Alan yoksa 0. - ram_mb (integer, opsiyonel) [Ornek: 4096] Toplam RAM (MB, listvs `ram`). Alan yoksa 0. - disk_gb (integer, opsiyonel) [Ornek: 40] Toplam disk kapasitesi (GB, listvs `space`). Alan yoksa 0. - bandwidth_gb (number(float), opsiyonel) [Ornek: 30720] Aylik bandwidth kotasi (GB, listvs `bandwidth`). Alan yoksa 0. - bandwidth_used_gb (number(float), opsiyonel) [Ornek: 12.5] Kullanilan bandwidth (GB, listvs `used_bandwidth`). Alan yoksa 0. - location (string, opsiyonel) [Ornek: "Germany"] VPS'in fiziksel lokasyonu — sunucunun bagli oldugu Virtualizor sunucu grubunun adi (`act=servergroups` sg_name; orn. "Germany", "Turkey"). serid -> sg_name haritasi APCu'da 300s onbelleklenir. Lokasyon cozulemezse (Virtualizor erisilemez / grup yok) bos doner. Bayi musteri panelinde hostname yaninda ulke bayragi + etiket olarak gosterilir. - ips (array, zorunlu) Virtualizor tarafinda atanmis tum IPv4/IPv6 adresleri - virt_type (string, zorunlu) [Ornek: "kvm"] Virtualization tipi (kvm, openvz, lxc, proxk, ...) - suspended (boolean, zorunlu) [Ornek: false] - service_type (string, opsiyonel) [degerler: "vps" | "dedicated" | "hosting"; Ornek: "vps"] Hizmet tipi (urunun Rabisu Master tipi). - management (string, opsiyonel) [degerler: "full" | "basic"; Ornek: "full"] `basic` ise panel islemleri (status/power/rebuild/vnc-token/password) 409 `management_unavailable` doner; musteriye credentials gosterilir. - virt_unavailable (boolean, zorunlu) [Ornek: false] true ise Virtualizor master upstream'i cevap vermedi (timeout/5xx), vps_id resolve edilemedi veya hizmet management=basic. UI bu durumda "VPS bilgileri suan getirilemiyor" uyarisi gosterebilir. ------------------------------------------------------------------------------ ServiceStatus (object) ------------------------------------------------------------------------------ Real-time VPS metrikleri (Virtualizor `act=vpsstat`). Cache YOK. `services_status` rate limit bucket'i altinda servis edilir. - service_id (integer, zorunlu) [Ornek: 7777] - vps_id (integer, zorunlu) [Ornek: 1234] - running (boolean, zorunlu) [Ornek: true] - cpu_percent (number(float), zorunlu) [Ornek: 12.5] 0.0-100.0 arasi anlik CPU kullanimi (VPS-relative — kendi cekirdeklerine gore). Enduser `act=cpu` -> cpu.percent'ten cekilir; admin used_cpu NODE-relative yaniltici oldugu icin KULLANILMAZ. - ram_used_mb (integer, zorunlu) [min 0; Ornek: 768] - ram_total_mb (integer, zorunlu) [min 0; Ornek: 2048] - disk_used_mb (integer, zorunlu) [min 0; Ornek: 5500] - disk_total_mb (integer, zorunlu) [min 0; Ornek: 40960] - net_in_bytes (integer(int64), zorunlu) [min 0; Ornek: 102400] ANLIK gelen ag trafigi (byte) — admin `vs_status.net_in` (poll araligi delta'si). Canli grafik kaynagi; her poll'da guncellenir. - net_out_bytes (integer(int64), zorunlu) [min 0; Ornek: 51200] ANLIK giden ag trafigi (byte) — admin `vs_status.net_out`. Canli grafik. - bandwidth_total_bytes (integer(int64), zorunlu) [min 0; Ornek: 1256550400] TOPLAM aylik ag trafigi (byte, birlesik in+out) — admin `vs_status.used_bandwidth` (GB → byte). Bayi UI ilk yuklemede 1 kere gosterir; anlik net_in/net_out canli poll ile guncellenir. ------------------------------------------------------------------------------ OsTemplateList (object) ------------------------------------------------------------------------------ Rebuild icin uygun OS sablonlari. APCu 5dk cache (`os_templates:v1:global`) — tum bayilerde paylasilir, reseller_id'den bagimsiz. - templates (array, zorunlu) ------------------------------------------------------------------------------ OsTemplate (object) ------------------------------------------------------------------------------ - id (integer, zorunlu) [Ornek: 270] Virtualizor OS template id (`osid`) - name (string, zorunlu) [Ornek: "CentOS Stream 9"] - virt_type (string, zorunlu) [Ornek: "kvm"] Hangi virtualization tipi icin kullanilabilir (kvm, openvz, ...) ------------------------------------------------------------------------------ PowerActionResult (object) ------------------------------------------------------------------------------ - status (string, zorunlu) [degerler: "power_action_executed"] - action (string, zorunlu) [degerler: "start" | "stop" | "restart" | "poweroff"] - vps_id (integer, zorunlu) [min 1; Ornek: 12345] - executed_at (string(date-time), zorunlu) [Ornek: "2026-04-25T06:01:00+00:00"] Server'in aksiyonu Virtualizor'a gonderdigi an (UTC ISO8601). ------------------------------------------------------------------------------ RebuildScheduledResult (object) ------------------------------------------------------------------------------ Asenkron rebuild job'i basariyla schedulelandi. Gercek isleme 5-30dk surer; bayi modulu sonra `GET /services/{id}/status` ile takip eder. - status (string, zorunlu) [degerler: "rebuild_scheduled"] - os_id (integer, zorunlu) [min 1; Ornek: 270] - vps_id (integer, zorunlu) [min 1; Ornek: 12345] - estimated_duration_seconds (integer, zorunlu) [Ornek: 600] Tahmini rebuild suresi (saniye). Sabit 600s (10dk orta-tahmin). Bayi UI countdown / polling penceresini bu degere gore ayarlamali. - root_password (string, opsiyonel, null olabilir) [Ornek: "Xkj9aF2pBm7nQrV3"] Rebuild sonrasi yeni root parolasi. **Iki kaynaktan biri olur:** 1. Bayi istek body'sinde `root_password` gondermisse — onu doner (echo, bayi-belirledigi parola). 2. Body'de `root_password` yoksa — API guvenli (16-char alfanumerik) bir parola UREITR ve burada doner. Her iki durumda da bu deger SADECE BU 1 RESPONSE'DA alinabilir; sonradan tekrar alinamaz (Virtualizor saklamiyor). Bayi UI'sinde kullaniciya gosterilmesi gerekir. **Davranis:** - KVM rebuild: parola VPS yeniden baslarken atanir (onboot). - LXC/OpenVZ rebuild: parola hemen atanir. **Loglama:** LogMasker `root_password` field'ini otomatik maskeler (log'da `***MASKED***` gorunur). ------------------------------------------------------------------------------ VncTokenResult (object) ------------------------------------------------------------------------------ Kisa omurlu (60s) NoVNC baglanti bilgisi. Bayi browser'i DIREKT `host:port/websockify_path` URL'sine baglanir; API proxy yapmaz. - vps_id (integer, zorunlu) [min 1; Ornek: 12345] - host (string, zorunlu) [Ornek: "node01.rabisu.com"] Virtualizor node hostname'i - port (integer, zorunlu) [min 1; max 65535; Ornek: 4083] Virtualizor musteri paneli (enduser) novnc proxy portu: `VIRTUALIZOR_DEFAULT_NOVNC_PORT` (varsayilan 4083; panel 80/443'te ise 443). HAM VNC/RFB portu (5902) DEGIL — o porta wss baglantisi WS 1006 verir. Tarayici `Sec-WebSocket-Protocol: binary` gondermeli (Virtualizor websockify aksi halde 400 doner). - websockify_path (string, zorunlu) [Ornek: "novnc/?virttoken=12345"] Token-li relative path (browser `wss://host:port/{path}` ile baglanir). Format: `novnc/?virttoken=`. - password (string, zorunlu) [Ornek: "Xkj9_short_lived"] Short-lived NoVNC parolasi (60s). LogMasker `vnc_password` alanini asla maskelemeden log'a yazmaz. - expires_at (string(date-time), zorunlu) [Ornek: "2026-04-25T06:01:00+00:00"] Server'in hesapladigi son gecerlilik ani (UTC ISO8601, now+60s). - keymap (string, opsiyonel) [Ornek: "tr"] VPS'in Virtualizor VNC klavye duzeni (`tr`, `en-us`...); ayarlanmamissa bos. Konsolda metni tus vurusu olarak yazarken varsayilan duzen icin ipucu. - ttl_seconds (integer, zorunlu) [degerler: 60; Ornek: 60] Sabit 60sn — client `expires_at`'i parse etmek yerine kolayca countdown yapabilir. ------------------------------------------------------------------------------ SuspendResult (object) ------------------------------------------------------------------------------ WHMCS moduleSuspend basarili — bizim provision DB'mizde status='suspended' set edildi. - status (string, zorunlu) [degerler: "suspended"] - service_id (integer, zorunlu) [min 1; Ornek: 12345] - vps_id (integer, zorunlu, null olabilir) [min 1; Ornek: 67890] - reason (string, zorunlu) [Ornek: "Non-payment"] - suspended_at (string(date-time), zorunlu) [Ornek: "2026-04-25T06:23:00+00:00"] ------------------------------------------------------------------------------ UnsuspendResult (object) ------------------------------------------------------------------------------ WHMCS moduleUnsuspend basarili — bizim provision DB'mizde status='active' set edildi (suspended_at audit icin korunur). - status (string, zorunlu) [degerler: "active"] - service_id (integer, zorunlu) [min 1; Ornek: 12345] - vps_id (integer, zorunlu, null olabilir) [min 1; Ornek: 67890] - unsuspended_at (string(date-time), zorunlu) [Ornek: "2026-04-25T06:24:00+00:00"] ------------------------------------------------------------------------------ UpgradeServiceResult (object) ------------------------------------------------------------------------------ POST /services/{id}/upgrade sonucu. `quote=true` ise sadece prorata teklifi (order/invoice/payment alanlari null). `quote=false` gercek upgrade: fark faturasi + kredi uygulama akisi (OrderService ile ayni). - service_id (integer, zorunlu) [min 1; Ornek: 12345] - quote (boolean, zorunlu) [Ornek: false] true ise sadece teklif (calconly), upgrade uygulanmadi. - currency (string, zorunlu) [Ornek: "TRY"] Bayinin WHMCS hesap para birimi (tblclients.currency); okunamazsa `BALANCE_DEFAULT_CURRENCY`. - price (string, opsiyonel, null olabilir) [Ornek: "TRY 45,00"] WHMCS'in formatli prorata tutari (gosterim icin birebir). - price_amount (number(float), opsiyonel, null olabilir) [Ornek: 45] Best-effort parse edilmis prorata tutari (liste fiyati). - discount (allOf, opsiyonel, null olabilir) Bayinin indirim grubu hizmetin urununu kapsiyorsa grup adi + oran; aksi halde null. Asagidakilerin birlesimi (allOf): → bkz. sema: ResellerDiscount - discounted_price_amount (number(float), opsiyonel, null olabilir) [Ornek: 36] Odenecek (indirimli) prorata tutari; yukseltme faturasi Rabisu'da bu oranla kesilir. Kapsam disinda null. - days_until_renewal (integer, opsiyonel, null olabilir) [Ornek: 18] - config_options (object, zorunlu) Uygulanan/teklif edilen config option map'i. - order_id (integer, opsiyonel, null olabilir) [Ornek: 9310] Gercek upgrade'de WHMCS order id (quote'ta null). - invoice_id (integer, opsiyonel, null olabilir) [Ornek: 8410] Fark faturasi id (quote'ta veya faturasiz upgrade'de null). - status (string, opsiyonel, null olabilir) [degerler: "pending_provision" | "pending_payment" | "payment_failed" | "active"; Ornek: "pending_provision"] Gercek upgrade sonuc durumu (quote'ta null). - payment_applied (boolean, zorunlu) [Ornek: true] Kredi otomatik uygulandi mi. - balance_after (number(float), opsiyonel, null olabilir) [Ornek: 155] Kredi uygulandiysa kalan bakiye. - notice (string, opsiyonel, null olabilir) [degerler: "insufficient_credit" | "auto_apply_credit_disabled"] Kredi uygulanamadiysa neden. ------------------------------------------------------------------------------ TerminateScheduledResult (object) ------------------------------------------------------------------------------ WHMCS moduleTerminate basarili — provisioning queue Virtualizor delvs'i async yapacak (queue cron + delvs ~120sn). Bizim provision DB'mizde status='terminated' set edildi. - status (string, zorunlu) [degerler: "termination_scheduled"] - service_id (integer, zorunlu) [min 1; Ornek: 12345] - vps_id (integer, opsiyonel, null olabilir) [Ornek: 67890] Pending/whitelabel servisler icin null olabilir. - terminated_at (string(date-time), zorunlu) [Ornek: "2026-04-25T06:25:00+00:00"] - estimated_completion_seconds (integer, zorunlu) [degerler: 120; Ornek: 120]