portal
27 uç · /api/crm/v1/portal
İstek örnekleri ucun GERÇEK metodu ve yolundan üretilir. 27 ucun elle doğrulanmış gövde örneği henüz yok; o uçlarda iskelet gövdesizdir — uydurma bir alan yazmıyoruz.
POST /api/crm/v1/portal/login-link — müşteri kendi giriş bağlantısını ister.
🔴 DAİMA 204 döner. "Bu e-posta kayıtlı değil" demek, bu kiracının müşteri listesini dışarıdan sorgulanabilir yapardı (adres sayımı). Kullanıcıya ekranda söylenen cümle de o yüzden koşulsuzdur: *"adresin kayıtlıysa bağlantı gönderildi"*. 🔴 Hız sınırı ZORUNLU: sınırsız bir uç, birinin kutusuna bağlantı yağdırmanın (posta bombası) ve adres sayımının yoludur.
curl -X POST "https://{slug}.crm.blesyum.app/api/crm/v1/portal/login-link" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}GET /api/crm/v1/portal/notify/status — yönetici ekranının okuduğu DURUM. Hangi
kanalın GERÇEKTEN çalışabileceğini söyler ("gösterilen kutu ÇALIŞIR"): SMS anahtarı kimlik yokken açılabilir ama hiçbir mesaj çıkmaz — ekran bunu buradan bilir.
curl -X GET "https://{slug}.crm.blesyum.app/api/crm/v1/portal/notify/status" \
-H "Authorization: Bearer <oturum-token>" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}GET /api/crm/v1/portal/oauth/google/callback — kiracısız, sabit host.
Kimlik doğrulaması tek kullanımlık state satırıyla yapılır; kiracı onun ön ekinden
çözülür. Korumalı bir router'a konulsaydı istek her seferinde 404/401 alırdı (çıplak
host hiçbir kiracıya çözülmez — Meta callback'in aynı gerekçesi).
curl -X GET "https://{slug}.crm.blesyum.app/api/crm/v1/portal/oauth/google/callback" \
-H "Authorization: Bearer <oturum-token>" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}GET /api/crm/v1/portal/push/subscriptions — müşterinin kaç cihazı abone + açık
anahtar. Tarayıcı kendi aboneliğini pushManager.getSubscription()tan bilir; bu uç
"sunucu da biliyor mu" sorusunu cevaplar (ölü abonelik silinmiş olabilir).
curl -X GET "https://{slug}.crm.blesyum.app/api/crm/v1/portal/push/subscriptions" \
-H "Authorization: Bearer <oturum-token>" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}Müşterinin tarayıcı aboneliğini yaz.
⚠ Çakışma anahtarı (modul, endpoint) ve endpoint TARAYICI PROFİLİNİNDİR (aynı
köken + aynı service worker + aynı VAPID anahtarı → aynı adres). Aynı tarayıcıda
önce temsilci sonra müşteri abone olursa satır SON ABONEYE geçer (user_id boşalır,
kisi_ref dolar — push_subs_sahip_chk "tam olarak biri"). Bu bilinçli: bir
tarayıcı profili aynı anda iki kimliğe bildirim gösteremez ve ekranda o an kim
oturum açmışsa bildirim ONA gitmelidir (ajan tarafının push::save kararıyla aynı).
curl -X POST "https://{slug}.crm.blesyum.app/api/crm/v1/portal/push/subscriptions" \
-H "Authorization: Bearer <oturum-token>" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}Müşterinin aboneliğini sil. Silme SAHİPLİKLE sınırlıdır (kisi_ref): gövdeye
başka birinin endpoint'ini yazan müşteri onun aboneliğini düşüremez (müşavirin "taşınan tablo, taşınmayan politika" dersi — kapı burada, açık yüklemle).
curl -X POST "https://{slug}.crm.blesyum.app/api/crm/v1/portal/push/unsubscribe" \
-H "Authorization: Bearer <oturum-token>" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}POST /api/crm/v1/portal/session — bağlantıyı tüket, portal oturumunu aç.
Oturum HttpOnly bir çereze yazılır; gövdede token DÖNMEZ. Panelin çekirdek
token'ı localStorageda duruyor ve bu bilinen bir açık (self-XSS yüzeyi);
yeni bir yüzeyi aynı açıkla doğurmanın sebebi yok.
curl -X POST "https://{slug}.crm.blesyum.app/api/crm/v1/portal/session" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}GET /api/crm/v1/portal/tickets — müşterinin kendi talepleri (simple-pagination).
2026-09-29: satır artık portal_talep::listein BEYAZ LİSTESİdir (ajan profilinin
to_jsonb(c) projeksiyonu search_vector/spam_*/ai_state sızdırıyordu) ve
bölme (status=open|closed|all), arama (query: konu ya da #numara), okunmamış
sayısı ile bölme sayaçlarını (counts) taşır. Sayaçlar listeyle AYNI yüklemden.
curl -X GET "https://{slug}.crm.blesyum.app/api/crm/v1/portal/tickets" \
-H "Authorization: Bearer <oturum-token>" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}POST /api/crm/v1/portal/tickets — müşteri yeni talep (server-authoritative user_id).
curl -X POST "https://{slug}.crm.blesyum.app/api/crm/v1/portal/tickets" \
-H "Authorization: Bearer <oturum-token>" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}GET /api/crm/v1/portal/tickets/{id} — kendi talep detayı.
idcurl -X GET "https://{slug}.crm.blesyum.app/api/crm/v1/portal/tickets/<id>" \
-H "Authorization: Bearer <oturum-token>" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}GET /api/crm/v1/portal/tickets/{id}/items — kendi talebinin mesajları (internal not GİZLİ).
idcurl -X GET "https://{slug}.crm.blesyum.app/api/crm/v1/portal/tickets/<id>/items" \
-H "Authorization: Bearer <oturum-token>" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}POST /api/crm/v1/portal/tickets/{id}/mark-as-solved — müşteri kendi talebini çözüldü işaretler.
idcurl -X POST "https://{slug}.crm.blesyum.app/api/crm/v1/portal/tickets/<id>/mark-as-solved" \
-H "Authorization: Bearer <oturum-token>" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}POST /portal/tickets/{id}/read — okuma izini şimdiye ilerlet. Detay GET okundu
idİŞARETLEMEZ: sekmede açık kalan bir talep, müşteri görmeden "okundu" sayılmasın; ön yüz bu ucu mesajlar ekranda göründüğünde çağırır.
curl -X POST "https://{slug}.crm.blesyum.app/api/crm/v1/portal/tickets/<id>/read" \
-H "Authorization: Bearer <oturum-token>" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}POST /api/crm/v1/portal/tickets/{id}/reply — kendi talebine yanıt (kilitli→red, reopen).
idcurl -X POST "https://{slug}.crm.blesyum.app/api/crm/v1/portal/tickets/<id>/reply" \
-H "Authorization: Bearer <oturum-token>" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}POST /portal/uploads (multipart, tek file) — müşterinin eki.
Üç sınır (widget ucunun ikizi): kimlik (müşteri kapısı) · hız (müşteri başına
WIDGET_UPLOAD_LIMIT / WIDGET_UPLOAD_WINDOW) · boyut ([crate::portal_dosya::MAX_BYTES]).
Tür BAYTLARDAN okunur (portal_dosya::musteri_eki_turu); saklanan MIME imzadan türer.
Dosyanın sahibi müşteridir (owner_id) — yanıt ucu yalnız ONUN bağlanmamış
dosyalarını kabul eder (portal_dosya::ekleri_dogrula).
curl -X POST "https://{slug}.crm.blesyum.app/api/crm/v1/portal/uploads" \
-H "Authorization: Bearer <oturum-token>" \
-H "Accept: application/json"{
"data": "…",
"trace_id": "01JC…"
}