KAP bildirim akışı (WebSocket)
Resmi KAP (Kamuyu Aydınlatma Platformu) bildirimleri, yayımlandıkça tek bir WebSocket bağlantısıyla sisteminize gelir — seçtiğiniz şirketler ve bildirim tiplerine göre süzülür, bağlantı koparsa kaldığı yerden boşluksuz devam eder.
Kaynak
Bildirimler resmi KAP veri yayınından, sunulduğu haliyle gelir — yeniden yazma yok, yapay zeka işlemesi yok. KAP'ın kendisi itme (push) yapmaz: PortGate KAP'ı birkaç saniyede bir yoklar, her yeni bildirimi saklar ve ardından size iter. Her bildirimin KAP tarafından verilen, ardışık ve boşluksuz bir disclosure_index numarası vardır; kaldığı yerden devam etmeyi ve tekilleştirmeyi kesin yapan bu numaradır.
/v1/kap REST akışı (news_id, yapay zeka özeti ve duygu analizi) farklı bir üründür. Bu akış ve /v1/kap-feed/disclosures yalnızca resmi bildirimi taşır ve KAP'ın kendi disclosure_index numarasını kullanır.1 — Bağlanın ve kimlik doğrulayın
Bağlanın, ardından ilk kare olarak 10 saniye içinde auth gönderin. Anahtarın kap kapsamına sahip olması gerekir; yoksa sunucu 4403 ile kapatır.
wss://api.theportfoy.com/portgate/v1/kap-feed/stream{"action": "auth", "api_key": "pg_live_…"}{"type": "auth_ok", "plan": "tester"}2 — Konulara abone olun
Enstrüman kimliklerine değil konulara abone olursunuz. Mesaj başına en fazla 100, bağlantı başına en fazla 200 aktif konu. Bilinmeyen konular invalid içinde geri döner; geçerli olanlar yine uygulanır. Birden çok konunuzla eşleşen bir bildirim tek kez gelir.
| Alan | Tür | Notlar |
|---|---|---|
| all | topic | Tüm bildirimler. |
| type:<TYPE> | topic | Tek bir bildirim tipi: FR (finansal rapor), ODA (özel durum açıklaması), DG (diğer), DUY (düzenleyici kurum bildirimi), CA (hak kullanımı / kurumsal işlem), FON (fon bildirimi). ÖDA ve DKB de kabul edilir; ODA ve DUY olarak geri döner. |
| symbol:<CODE> | topic | Öneksiz bir KAP kodu: symbol:THYAO ya da symbol:UPG gibi bir fon kodu. Kod, bildirimin symbols listesindeyse eşleşir. Burada piyasa öneki yoktur — symbol:bist:THYAO geçersizdir. |
{"action": "subscribe", "ids": ["symbol:THYAO", "type:FR", "bist:THYAO"]}
{"action": "unsubscribe", "ids": ["type:FR"]}{"type": "subscribed", "ids": ["symbol:THYAO", "type:FR"], "invalid": ["bist:THYAO"]}
{"type": "unsubscribed", "ids": ["type:FR"]}3 — Bildirim karesi
Her bildirim tek bir kare olarak gelir. Canlı karelerde replay false, kaldığı yerden devam ederken gönderilen karelerde true'dur (bölüm 5).
{"type": "disclosure", "replay": false, "data": {
"disclosure_index": 1671665,
"type": "CA", "class": "ODA",
"symbols": ["PCILT"], "filer_codes": ["PCILT"],
"filer_title": "PC İLETİŞİM VE MEDYA HİZMETLERİ SANAYİ TİCARET A.Ş.",
"published_at": "2026-10-03T09:56:03Z",
"reason": "UPD", "related_index": 1671010,
"subject": {"tr": "Payların Geri Alınmasına İlişkin Bildirim",
"en": "Notification Regarding Share Buy-Back"},
"summary": {"tr": "02.10.2026 Tarihli Pay Geri Alım İşlemleri", "en": null},
"link": "https://www.kap.org.tr/Bildirim/1671665",
"attachment_count": 0,
"year": null, "period": null, "consolidation": null
}}| Alan | Tür | Notlar |
|---|---|---|
| disclosure_index | integer | KAP'ın ardışık bildirim numarası. Tekildir; tekilleştirme ve devam etme için kullanın. |
| type | string | FR · ODA · DG · DUY · CA · FON |
| class | string | Sunulduğu haliyle KAP bildirim sınıfı. |
| symbols | string[] | Bildirimin ilgili olduğu tüm kodlar: bildirim sahibinin kodları, fon bildirimlerinde fon kodu, Borsa İstanbul devre kesici bildirimlerinde işlemi durdurulan hisse. Kodu olmayan piyasa geneli bildirimlerde boştur. |
| filer_codes | string[] | Bildirim sahibinin kendi borsa kodları, sunulduğu haliyle. |
| filer_title | string | null | Bildirim sahibinin unvanı, sunulduğu haliyle. |
| published_at | string | null | KAP yayım zamanı, ISO-8601 UTC. İstanbul UTC+3'tür — göstermeden önce çevirin. |
| reason | string | null | Asıl bildirimde NEW; önceki bir bildirimi güncelliyor, düzeltiyor ya da iptal ediyorsa UPD, CORR veya CANC. |
| related_index | integer | null | Bu bildirimin güncellediği, düzelttiği ya da iptal ettiği önceki bildirim; NEW'de null. |
| subject | {tr, en} | null | KAP konusu, TR/EN, sunulduğu haliyle. |
| summary | {tr, en} | null | Bildirim sahibinin kendi özeti, TR/EN, yazdığı haliyle. Çoğu zaman boştur (null). |
| link | string | null | Bildirimin kap.org.tr sayfası. |
| attachment_count | integer | Ek sayısı (yoksa 0). Dosyaları REST detayından alın. |
| year · period · consolidation | string | null | Yalnızca finansal raporlarda (FR): mali yıl, KAP'ın dönem etiketi (ör. Yıllık, 6 Aylık) ve CS/NC. Diğer türlerde null. FR türünde de KAP göndermediğinde bir değer null olabilir — ör. faaliyet raporlarında ya da sorumluluk beyanlarında consolidation. |
Her alan her zaman bulunur; KAP'ın vermediği değer null'dır. Aynı bildirimin canlı ve replay kareleri birebir aynıdır.
reason, asıl bildirimde NEW; bildirim daha önceki bir bildirimi güncelliyor, düzeltiyor ya da iptal ediyorsa UPD, CORR veya CANC olur ve related_index o önceki bildirimi gösterir. Yukarıdaki örnek, 1671010'un güncellemesidir.
4 — Finansal kalemler ve ekler
Kare, bildirimin kimliğini ve KAP'ın kendi tanımlayıcı alanlarını taşır. Finansal rapor kalemleri ve ek bağlantıları için kaydı numarasıyla GET /v1/kap-feed/disclosures/{disclosure_index} ucundan (aynı kap kapsamı).
curl -H "X-API-Key: $PORTGATE_KEY" \
"https://api.theportfoy.com/portgate/v1/kap-feed/disclosures/1671665"{
"disclosure_index": 1671665,
"type": "CA",
"reason": "UPD",
"related_index": 1671010,
"published_at": "2026-10-03T09:56:03Z",
"symbols": ["PCILT"],
"subject": {"tr": "Payların Geri Alınmasına İlişkin Bildirim",
"en": "Notification Regarding Share Buy-Back"},
"summary": {"tr": "02.10.2026 Tarihli Pay Geri Alım İşlemleri", "en": null},
"link": "https://www.kap.org.tr/Bildirim/1671665",
"attachment_count": 0,
...
}Bildirimin KAP'ta sunulduğu haliyle tam içeriği bir çağrı uzağınızda: GET /v1/kap-feed/disclosures/{index}/body
5 — Boşluksuz devam
İşlediğiniz en yüksek disclosure_index'i saklayın. Her yeniden bağlanmada aboneliğinize last_seen_index olarak ekleyin. Sunucu sırasıyla:
- subscribed ile yanıt verir,
- last_seen_index'ten sonraki, konularınızla eşleşen tüm kayıtlı bildirimleri eskiden yeniye, replay: true ile gönderir,
- replay_done gönderir,
- canlı devam eder. Tekrar sırasında gelen canlı bildirimler bekletilir ve replay_done'dan hemen sonra gönderilir — kayıp yok, çift yok.
{"action": "subscribe", "ids": ["all"], "last_seen_index": 1671664}{"type": "subscribed", "ids": ["all"], "invalid": []}
{"type": "disclosure", "replay": true, "data": {"disclosure_index": 1671665, ...}}
{"type": "disclosure", "replay": true, "data": {"disclosure_index": 1671666, ...}}
{"type": "replay_done", "last_index": 1671666, "truncated": false}
{"type": "disclosure", "replay": false, "data": {"disclosure_index": 1671667, ...}}Bir tekrar en fazla 1000 bildirim kapsar. Daha fazlası varsa truncated true olur: kalanını GET /v1/kap-feed/disclosures ucundan sayfalayarak alın (en yeni önce, imleçle sayfalama) ve daha yeni bir last_seen_index ile yeniden abone olun.
6 — Bağlantıyı canlı tutun
{"action": "ping"} mesajını her 30 saniyede bir gönderin. Bildirimler arasında dakikalar (gece ve hafta sonu saatler) olabilir; ping olmadan atıl bağlantı ağ yolu üzerinde kapatılır. Sunucu {"type": "pong"} ile yanıt verir.Tam örnek (yeniden bağlanma + devam + tekilleştirme)
const URL = "wss://api.theportfoy.com/portgate/v1/kap-feed/stream";
const TOPICS = ["symbol:THYAO", "type:FR"];
let lastSeen = loadLastSeen(); // your durable store; null on first run
const seen = new Set(); // recent indexes, for at-least-once dedupe
let backoff = 1000;
function connect() {
const ws = new WebSocket(URL);
let pingTimer;
ws.onopen = () => ws.send(JSON.stringify({ action: "auth", api_key: KEY }));
ws.onmessage = (ev) => {
const msg = JSON.parse(ev.data);
if (msg.type === "auth_ok") {
backoff = 1000;
const sub = { action: "subscribe", ids: TOPICS };
if (lastSeen !== null) sub.last_seen_index = lastSeen;
ws.send(JSON.stringify(sub));
pingTimer = setInterval(
() => ws.send(JSON.stringify({ action: "ping" })), 30_000);
} else if (msg.type === "disclosure") {
const d = msg.data;
if (seen.has(d.disclosure_index)) return; // duplicate
seen.add(d.disclosure_index);
handle(d); // queue it; keep this fast
lastSeen = Math.max(lastSeen ?? 0, d.disclosure_index);
saveLastSeen(lastSeen);
} else if (msg.type === "replay_done" && msg.truncated) {
backfillFromRest(lastSeen); // GET /v1/kap-feed/disclosures
} else if (msg.type === "error") {
console.warn(msg.code, msg.message);
}
};
ws.onclose = (ev) => {
clearInterval(pingTimer);
if (ev.code === 4401 || ev.code === 4403) return; // fix the key
setTimeout(connect, backoff + Math.random() * 1000);
backoff = Math.min(backoff * 2, 60_000);
};
}
connect();Hatalar ve kapanış kodları
| Alan | Tür | Notlar |
|---|---|---|
| 4401 | close | 10 sn içinde auth yok, ilk kare auth değil ya da anahtar geçersiz/iptal — anahtarı düzeltin, döngüde yeniden denemeyin |
| 4403 | close | Anahtarda kap kapsamı yok — yeniden denemeyin |
| 4400 | close | Üç hatalı mesaj (bozuk JSON, bilinmeyen action, hatalı alanlar) |
| 4429 | close | slow_consumer: istemciniz okumayı bıraktı ve 512 karelik tamponu doldu. Bildirimler asla sessizce düşürülmez — last_seen_index ile yeniden bağlanın, tekrar boşluğu doldurur |
| 1013 | close | stream_unavailable: canlı yayın geçici olarak kullanılamıyor — geri çekilmeyle yeniden deneyin |
| 1011 | close | server_error: dahili aktarım hatası — geri çekilmeyle yeniden deneyin |
| 1001 | close | Sunucu kapanıyor (dağıtım/yeniden başlatma) — last_seen_index ile yeniden bağlanın |
| error{code:"subscription_limit"} | message | 200 aktif konu aşılırdı; aboneliğin tamamı reddedilir |
| error{code:"bad_message"} | message | Hatalı mesaj; üçüncü hataya kadar bağlantıyı kapatmaz |
| error{code:"server_error"} + replay_done | message | Tekrar başarısız; ardından truncated: true ile replay_done gelir — eksiği GET /v1/kap-feed/disclosures ile tamamlayın |
Ölçüm
Kimliği doğrulanmış bağlantı başına bir birim (/v1/kap-feed/stream) artı teslim edilen her bildirim karesi için bir birim, canlı ya da tekrar (/v1/kap-feed/stream:events). İkisi de kullanımınızda diğer REST çağrıları gibi görünür. Eski /v1/kap/stream adresindeki bağlantılar /v1/kap/stream ve /v1/kap/stream:events altında ölçülür.
İyi uygulamalar
- last_seen_index'i kalıcı bir yerde (veritabanı, dosya) ve yalnızca bildirimi işledikten sonra saklayın — böylece bir çökme ya da dağıtım hiçbir bildirimi kaybettirmez.
- Üstel geri çekilme ve rastgele gecikmeyle yeniden bağlanın. 4401/4403'te yeniden bağlanmayın: bunlar anahtar düzeltmesi gerektirir.
- Akışın yalnızca bir kısmına ihtiyacınız varsa dar abone olun (symbol: ya da type:) — daha az kare, daha az birim.
- Okuma döngüsünü hızlı tutun: yavaş işleri döngü içinde yapmak yerine bildirimleri bir kuyruğa/işçiye verin; aksi hâlde 4429 riski doğar.
