Fetch company and fund disclosures from https://www.kap.org.tr
Project description
kap-client
kap-client is a type-safe Python client for KAP (Kamuyu Aydınlatma Platformu) — Turkey's Public Disclosure Platform.
Fetch company and fund disclosures, browse investment fund lists, and download disclosure attachments — all through a clean, synchronous, Pythonic API. Perfect for building financial dashboards, compliance tools, and investment research apps.
v0.1 — Production-ready, fully tested, zero breaking changes.
✨ Features
- 📢 Company disclosures — search by BIST ticker or KAP OID, with optional subject filtering
- 📋 Fund disclosures — search across all KAP fund groups (YF, BYF, EYF, OKS, GMF, …)
- � Per-fund filter endpoint —
fetch_fund_disclosures_by_filterqueries a single fund by KAP OID, no calendar-year limit, server-side filtering - �🏢 Company & fund lists — fully enumerated and cached per session
- 📎 Attachment parsing — automatically extracts file links from disclosure HTML pages
- 🔎 Subject filtering — narrow results using
FundSubjectOID constants - ⏱️ Retry & back-off — 3 attempts with exponential back-off;
RateLimitErroron 429 - 🛡️ Type-safe — full Pydantic v2 validation, frozen domain models, mypy compatible
- ⚡ Minimal dependencies — only
httpxandpydantic - 🐍 Modern Python — 3.10+ with context manager support
📦 Installation
pip install kap-client
Other package managers
# uv
uv pip install kap-client
# Poetry
poetry add kap-client
🚀 Quick start
from kap_client import Kap, FundGroup
with Kap() as kap:
# Fetch THYAO disclosures for Q1 2024
disclosures = kap.fetch_disclosures("THYAO", "2024-01-01", "2024-03-31")
for d in disclosures:
print(d.publish_datetime, d.subject, d.url)
# Download attachments from the first disclosure
if disclosures and disclosures[0].has_attachment:
attachments = kap.fetch_attachments(disclosures[0].index)
for a in attachments:
print(a.filename, a.url)
# Browse active Yatırım Fonları
funds = kap.fetch_funds(FundGroup.YATIRIM_FONLARI)
print(f"{len(funds)} active YF funds found")
📖 Usage Examples
Basic: Fetch company disclosures by ticker
from kap_client import Kap
with Kap() as kap:
disclosures = kap.fetch_disclosures("THYAO", "2024-01-01", "2024-12-31")
for d in disclosures:
print(f"[{d.publish_datetime:%Y-%m-%d %H:%M}] {d.subject}")
print(f" → {d.url}")
Disclosures with attachment download
from kap_client import Kap
with Kap() as kap:
disclosures = kap.fetch_disclosures("EREGL", "2024-01-01", "2024-12-31")
for d in disclosures:
if d.has_attachment:
attachments = kap.fetch_attachments(d.index)
for a in attachments:
print(f" {a.filename} → {a.url}")
Resolve ticker to Company object
from kap_client import Kap
with Kap() as kap:
co = kap.find_company("TCELL")
print(f"{co.name} (OID: {co.oid})")
# Use OID directly for subsequent queries — no extra HTTP request
disclosures = kap.fetch_disclosures(co.oid, "2024-01-01", "2024-03-31")
print(f"{len(disclosures)} disclosures found")
Batch: Reuse one context manager for multiple companies
from kap_client import Kap
tickers = ["THYAO", "EREGL", "TCELL", "AKBNK"]
with Kap() as kap:
# Company list is fetched once and cached for all find_company() calls
for ticker in tickers:
co = kap.find_company(ticker)
disclosures = kap.fetch_disclosures(co.oid, "2024-01-01", "2024-12-31")
print(f"{ticker}: {len(disclosures)} disclosures")
Browse and filter investment funds
from kap_client import Kap, FundGroup
with Kap() as kap:
# List active Yatırım Fonları (YF)
funds = kap.fetch_funds(FundGroup.YATIRIM_FONLARI)
print(f"{len(funds)} active YF funds")
# Include liquidated funds too
all_funds = kap.fetch_funds(FundGroup.YATIRIM_FONLARI, include_liquidated=True)
inactive = [f for f in all_funds if not f.is_active]
print(f"{len(inactive)} liquidated funds")
# List portfolio management companies for BYF group
members = kap.fetch_fund_members(FundGroup.BORSA_YATIRIM_FONLARI)
for m in members:
print(m.name)
Fund disclosures
Note: The KAP API requires
start_dateandend_dateto fall within the same calendar year. Use a year-by-year loop for multi-year searches.
from kap_client import Kap, FundGroup
with Kap() as kap:
disclosures = kap.fetch_fund_disclosures(
"2024-01-01", "2024-12-31",
fund_group=FundGroup.YATIRIM_FONLARI,
fund_code="AFA",
)
for d in disclosures:
print(f"[{d.publish_datetime:%Y-%m-%d}] {d.subject}")
Latest portfolio report via per-fund filter endpoint (no date-range limit)
Use fetch_fund_disclosures_by_filter when you know the fund's KAP OID. This endpoint has no same-year constraint and filters server-side — much faster for large date ranges.
from kap_client import Kap, FundSubject
# Fund OID is the `oid` field from a Fund object returned by fetch_funds()
THF_OID = "4028328c950ba8c70195140f682921da"
with Kap() as kap:
disclosures = kap.fetch_fund_disclosures_by_filter(
fund_oid=THF_OID,
subject_oid=FundSubject.PORTFOY_DAGILIM_RAPORU.value,
days=365,
)
if disclosures:
latest = disclosures[0] # sorted newest first
attachments = kap.fetch_attachments(latest.index)
for a in attachments:
print(f"{a.filename} → {a.url}")
To look up a fund's OID dynamically:
from kap_client import Kap, FundGroup, FundSubject
with Kap() as kap:
funds = kap.fetch_funds(FundGroup.YATIRIM_FONLARI)
fund = next(f for f in funds if f.code == "THF")
disclosures = kap.fetch_fund_disclosures_by_filter(
fund_oid=fund.oid,
subject_oid=FundSubject.PORTFOY_DAGILIM_RAPORU.value,
days=365,
)
for d in disclosures:
print(f"[{d.publish_datetime:%Y-%m-%d}] {d.subject}")
Latest portfolio report (portföy dağılım raporu) via byCriteria endpoint
from datetime import date
from kap_client import Kap, FundGroup
from kap_client._endpoints import FundSubject
with Kap() as kap:
for year in range(date.today().year, 2019, -1):
disclosures = kap.fetch_fund_disclosures(
f"{year}-01-01", f"{year}-12-31",
fund_group=FundGroup.YATIRIM_FONLARI,
fund_code="THF",
subject_oids=[FundSubject.PORTFOY_DAGILIM_RAPORU.value],
)
if disclosures:
latest = disclosures[0]
attachments = kap.fetch_attachments(latest.index)
for a in attachments:
print(f"{a.filename} → {a.url}")
break
Latest izahname (prospectus) for a fund
from datetime import date
from kap_client import Kap, FundGroup
from kap_client._endpoints import FundSubject
with Kap() as kap:
for year in range(date.today().year, 2012, -1):
disclosures = kap.fetch_fund_disclosures(
f"{year}-01-01", f"{year}-12-31",
fund_group=FundGroup.YATIRIM_FONLARI,
fund_code="TLY",
subject_oids=[FundSubject.IZAHNAME.value],
)
if disclosures:
attachments = kap.fetch_attachments(disclosures[0].index)
for a in attachments:
print(f"{a.filename} → {a.url}")
break
Filter by subject using FundSubject
from kap_client import Kap, FundSubject
with Kap() as kap:
disclosures = kap.fetch_disclosures(
"THYAO",
"2024-01-01",
"2024-12-31",
subject_oids=[FundSubject.OZEL_DURUM_ACIKLAMASI.value],
)
print(f"{len(disclosures)} özel durum açıklaması")
Integration: Export to Pandas
import pandas as pd
from kap_client import Kap
with Kap() as kap:
disclosures = kap.fetch_disclosures("THYAO", "2024-01-01", "2024-12-31")
df = pd.DataFrame([
{
"date": d.publish_datetime.date(),
"subject": d.subject,
"type": d.disclosure_type,
"has_attachment": d.has_attachment,
"is_corrective": d.is_corrective,
"url": d.url,
}
for d in disclosures
])
print(df.head())
print(f"\nTotal disclosures: {len(df)}")
print(f"With attachments: {df['has_attachment'].sum()}")
Error handling
from kap_client import Kap, KapError, RateLimitError, EmptyResponseError, CompanyNotFoundError
try:
with Kap() as kap:
co = kap.find_company("XXXXXX")
disclosures = kap.fetch_disclosures(co.oid, "2024-01-01", "2024-12-31")
except CompanyNotFoundError as e:
print(f"Ticker not found: {e.ticker}")
except EmptyResponseError:
print("No disclosures in selected date range")
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds")
except KapError as e:
print(f"KAP error: {e}")
📚 API Reference
Kap(timeout: float = 30.0)
Context manager for managing HTTP connections and caches.
with Kap(timeout=15.0) as kap:
disclosures = kap.fetch_disclosures("THYAO", "2024-01-01", "2024-12-31")
| Parameter | Type | Default | Description |
|---|---|---|---|
timeout |
float |
30.0 |
Per-request HTTP timeout in seconds |
Kap.fetch_companies(*, refresh=False) -> list[Company]
Returns all KAP-registered companies. Results are cached per instance — the second call returns the cached list without a network request. Pass refresh=True to force a fresh fetch.
with Kap() as kap:
companies = kap.fetch_companies()
print(f"{len(companies)} companies")
| Parameter | Type | Default | Description |
|---|---|---|---|
refresh |
bool |
False |
Bypass cache and fetch fresh data |
Kap.find_company(ticker, *, refresh=False) -> Company
Resolve a BIST ticker to a Company object. Ticker matching is case-insensitive.
with Kap() as kap:
co = kap.find_company("thyao") # same as "THYAO"
print(co.oid, co.name)
Raises CompanyNotFoundError if the ticker is not in the KAP member list.
Kap.fetch_funds(fund_group, *, include_liquidated=False, refresh=False) -> list[Fund]
Returns the fund list for a given group. Results are cached per (group, include_liquidated) combination.
with Kap() as kap:
funds = kap.fetch_funds(FundGroup.YATIRIM_FONLARI)
funds = kap.fetch_funds("YF") # string value accepted
all_funds = kap.fetch_funds("EYF", include_liquidated=True)
| Parameter | Type | Default | Description |
|---|---|---|---|
fund_group |
FundGroup | str |
— | Fund group enum or string value ("YF", "BYF", …) |
include_liquidated |
bool |
False |
Also return liquidated / tasfiye funds |
refresh |
bool |
False |
Bypass cache and fetch fresh data |
Kap.fetch_fund_members(fund_group, *, refresh=False) -> list[Company]
Returns portfolio management companies (kurucu/yönetici) for the given fund group.
Kap.fetch_disclosures(company, start_date, end_date, *, subject_oids=None) -> list[Disclosure]
Fetch company disclosures for a date range. Returns results sorted newest first.
with Kap() as kap:
# By ticker
disclosures = kap.fetch_disclosures("THYAO", "2024-01-01", "2024-12-31")
# By OID (no extra company lookup)
disclosures = kap.fetch_disclosures(co.oid, "2024-01-01", "2024-12-31")
# With subject filter
disclosures = kap.fetch_disclosures(
"THYAO", "2024-01-01", "2024-12-31",
subject_oids=[FundSubject.OZEL_DURUM_ACIKLAMASI.value],
)
| Parameter | Type | Default | Description |
|---|---|---|---|
company |
str |
— | BIST ticker ("THYAO") or raw KAP OID hex string |
start_date |
str | date | datetime |
— | Range start, inclusive ("YYYY-MM-DD" or date/datetime) |
end_date |
str | date | datetime |
— | Range end, inclusive |
subject_oids |
list[str] | None |
None |
Optional FundSubject OID values to filter results |
Kap.fetch_fund_disclosures(start_date, end_date, *, fund_code=None, fund_group=None, subject_oids=None) -> list[Disclosure]
Fetch fund disclosures for a date range. Returns results sorted newest first.
Constraint:
start_dateandend_datemust fall within the same calendar year. Cross-year ranges return HTTP 500. Use a year loop for multi-year searches.
with Kap() as kap:
disclosures = kap.fetch_fund_disclosures(
"2024-01-01", "2024-12-31",
fund_group=FundGroup.YATIRIM_FONLARI,
fund_code="THF",
)
| Parameter | Type | Default | Description |
|---|---|---|---|
start_date |
str | date | datetime |
— | Range start, inclusive ("YYYY-MM-DD") |
end_date |
str | date | datetime |
— | Range end, inclusive (must be same year as start) |
fund_code |
str | None |
None |
Short fund code filter, e.g. "THF" (client-side) |
fund_group |
FundGroup | str | None |
None |
Fund group filter, e.g. FundGroup.YATIRIM_FONLARI |
subject_oids |
list[str] | None |
None |
Optional FundSubject OID values |
Kap.fetch_fund_disclosures_by_filter(fund_oid, subject_oid, days=365) -> list[Disclosure]
Fetch disclosures for a single fund using the KAP per-fund filter endpoint (GET /tr/api/disclosure/filter/FILTERYFBF/{fund_oid}/{subject_oid}/{days}). Results are sorted newest first.
Key advantage: unlike fetch_fund_disclosures, this endpoint has no calendar-year constraint and filtering is done server-side, making it faster and more reliable for large date ranges.
with Kap() as kap:
funds = kap.fetch_funds(FundGroup.YATIRIM_FONLARI)
fund = next(f for f in funds if f.code == "TLY")
disclosures = kap.fetch_fund_disclosures_by_filter(
fund_oid=fund.oid,
subject_oid=FundSubject.IZAHNAME.value,
days=730,
)
| Parameter | Type | Default | Description |
|---|---|---|---|
fund_oid |
str |
— | KAP fund OID (32-char hex, from Fund.oid) |
subject_oid |
str |
— | FundSubject OID value (use FundSubject.XXX.value) |
days |
int |
365 |
Look-back window in days; no upper limit enforced by the API |
Kap.fetch_attachments(disclosure_index: int) -> list[Attachment]
Fetches attachment metadata for a disclosure via the KAP JSON API and returns direct download URLs. Returns an empty list if the disclosure has no attachments.
Download URLs have the form https://www.kap.org.tr/tr/api/file/download/{objId}.
with Kap() as kap:
disclosures = kap.fetch_disclosures("THYAO", "2024-01-01", "2024-12-31")
for d in disclosures:
if d.has_attachment:
for a in kap.fetch_attachments(d.index):
print(a.filename, a.url)
🗂️ Domain Models
Disclosure
| Field | Type | Description |
|---|---|---|
index |
int |
Unique KAP disclosure number |
publish_datetime |
datetime |
Publication timestamp |
company_name |
str |
Issuer name |
fund_code |
str |
Short fund code (e.g. "THF"); empty for company disclosures |
stock_codes |
str |
BIST ticker(s); empty for non-listed issuers |
subject |
str |
Disclosure topic |
summary |
str |
Short summary / teaser text; may be empty |
disclosure_type |
str |
Type classification |
has_attachment |
bool |
Whether file attachments are available |
is_late |
bool |
Filed after deadline |
is_corrective |
bool |
Correction of a prior disclosure |
is_english |
bool |
English-language disclosure |
url |
str |
Full URL to the KAP disclosure page |
Attachment
| Field | Type | Description |
|---|---|---|
filename |
str |
Original file name |
url |
str |
Direct download URL |
Company
| Field | Type | Description |
|---|---|---|
oid |
str |
KAP hex OID (32 chars) |
name |
str |
Official registered name |
ticker |
str |
BIST stock code; empty for non-listed entities |
Fund
| Field | Type | Description |
|---|---|---|
oid |
str |
KAP hex OID (32 chars) |
code |
str |
Short fund code (e.g. "AFA") |
title |
str |
Full fund name |
fund_type |
str |
e.g. "Hisse Senedi Fonu" |
fund_group |
FundGroup |
Enum value |
is_active |
bool |
False for liquidated funds |
🏷️ FundGroup Enum
| Value | Label |
|---|---|
FundGroup.BORSA_YATIRIM_FONLARI |
"BYF" — Borsa Yatırım Fonları |
FundGroup.YATIRIM_FONLARI |
"YF" — Yatırım Fonları |
FundGroup.EMEKLILIK_YATIRIM_FONLARI |
"EYF" — Emeklilik Yatırım Fonları |
FundGroup.OKS_EMEKLILIK_YATIRIM_FONLARI |
"OKS" — OKS Emeklilik Yatırım Fonları |
FundGroup.YABANCI_YATIRIM_FONLARI |
"YYF" — Yabancı Yatırım Fonları |
FundGroup.VARLIK_FINANSMAN_FONLARI |
"VFF" — Varlık Finansman Fonları |
FundGroup.KONUT_FINANSMAN_FONLARI |
"KFF" — Konut Finansman Fonları |
FundGroup.GAYRIMENKUL_YATIRIM_FONLARI |
"GMF" — Gayrimenkul Yatırım Fonları |
FundGroup.GIRISIM_SERMAYESI_FONLARI |
"GSF" — Girişim Sermayesi Fonları |
FundGroup.PROJE_FINANSMAN_FONLARI |
"PFF" — Proje Finansman Fonları |
FundGroup.TASFIYE_EDILEN_YATIRIM_FONLARI |
"TEYF" — Tasfiye Edilen Yatırım Fonları |
String values (e.g. "YF") are accepted everywhere a FundGroup is expected.
⚠️ Exceptions
| Exception | When raised |
|---|---|
KapError |
Base exception for all kap-client errors |
RateLimitError |
HTTP 429 after all retries exhausted; has .retry_after: float | None |
EmptyResponseError |
Successful response but empty data list |
CompanyNotFoundError |
Ticker not found in KAP member list; has .ticker: str |
🛠️ Development
git clone https://github.com/semudu/kap-client
cd kap-client
pip install -e ".[dev]"
make test # run tests
make lint # ruff lint + format check
make typecheck # mypy strict
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kap_client-1.1.0.tar.gz.
File metadata
- Download URL: kap_client-1.1.0.tar.gz
- Upload date:
- Size: 67.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a832371ddb0679640ed777e28b48ba8582f839307c3dde5cff9c0b650325bf61
|
|
| MD5 |
ae691570595c12981c00ab5aac17ba7c
|
|
| BLAKE2b-256 |
cf9d83e7799f8290051ba3c7fc03471e05dd5e3334fee6e7b005cc14fdfbe3a2
|
Provenance
The following attestation bundles were made for kap_client-1.1.0.tar.gz:
Publisher:
release.yml on semudu/kap-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kap_client-1.1.0.tar.gz -
Subject digest:
a832371ddb0679640ed777e28b48ba8582f839307c3dde5cff9c0b650325bf61 - Sigstore transparency entry: 1429406606
- Sigstore integration time:
-
Permalink:
semudu/kap-client@c4c14e52d91f3c0a3522d706095d45075e9ce2cd -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/semudu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c4c14e52d91f3c0a3522d706095d45075e9ce2cd -
Trigger Event:
push
-
Statement type:
File details
Details for the file kap_client-1.1.0-py3-none-any.whl.
File metadata
- Download URL: kap_client-1.1.0-py3-none-any.whl
- Upload date:
- Size: 21.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea01048915d3936c1013bea900ccd6cd87e07171e0fad69b21ee937f35bd2984
|
|
| MD5 |
ea1b9220a18d432a542970c9a43c127d
|
|
| BLAKE2b-256 |
46201437f27261cb4f2dde5f8f760b979b9f0cbaac1f1b08ef9bc9495ef07858
|
Provenance
The following attestation bundles were made for kap_client-1.1.0-py3-none-any.whl:
Publisher:
release.yml on semudu/kap-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kap_client-1.1.0-py3-none-any.whl -
Subject digest:
ea01048915d3936c1013bea900ccd6cd87e07171e0fad69b21ee937f35bd2984 - Sigstore transparency entry: 1429406610
- Sigstore integration time:
-
Permalink:
semudu/kap-client@c4c14e52d91f3c0a3522d706095d45075e9ce2cd -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/semudu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c4c14e52d91f3c0a3522d706095d45075e9ce2cd -
Trigger Event:
push
-
Statement type: