Skip to main content

Fetch company and fund disclosures from https://www.kap.org.tr

Reason this release was yanked:

buggy

Project description

kap-client

PyPI - Version PyPI - Python Version PyPI - License CI Docs

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, …)
  • 🏢 Company & fund lists — fully enumerated and cached per session
  • 📎 Attachment parsing — automatically extracts file links from disclosure HTML pages
  • 🔎 Subject filtering — narrow results using FundSubject OID constants
  • ⏱️ Retry & back-off — 3 attempts with exponential back-off; RateLimitError on 429
  • 🛡️ Type-safe — full Pydantic v2 validation, frozen domain models, mypy compatible
  • ⚡ Minimal dependencies — only httpx and pydantic
  • 🐍 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

from kap_client import Kap, FundGroup

with Kap() as kap:
    funds = kap.fetch_funds(FundGroup.YATIRIM_FONLARI)
    target = next(f for f in funds if f.code == "AFA")

    disclosures = kap.fetch_fund_disclosures(
        fund=target,
        fund_group=FundGroup.YATIRIM_FONLARI,
        start_date="2024-01-01",
        end_date="2024-12-31",
    )
    for d in disclosures:
        print(f"[{d.publish_datetime:%Y-%m-%d}] {d.subject}")

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(fund, fund_group, start_date, end_date, *, subject_oids=None) -> list[Disclosure]

Fetch fund disclosures for a date range. Returns results sorted newest first.

Parameter Type Default Description
fund Fund | str Fund instance or raw fund OID hex string
fund_group FundGroup | str Required even when passing a Fund object
start_date str | date | datetime Range start, inclusive
end_date str | date | datetime Range end, inclusive
subject_oids list[str] | None None Optional subject filter

Kap.fetch_attachments(disclosure_index: int) -> list[Attachment]

Fetches the HTML detail page for a disclosure and parses all file attachment links. Returns an empty list if the disclosure has no attachments.

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
stock_codes str BIST ticker(s); empty for non-listed issuers
subject str Disclosure topic
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ı

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kap_client-1.0.0.tar.gz (62.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kap_client-1.0.0-py3-none-any.whl (18.9 kB view details)

Uploaded Python 3

File details

Details for the file kap_client-1.0.0.tar.gz.

File metadata

  • Download URL: kap_client-1.0.0.tar.gz
  • Upload date:
  • Size: 62.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for kap_client-1.0.0.tar.gz
Algorithm Hash digest
SHA256 2773db1b2b2647c47989454dd80cb4017c79568a10ea139780b08211d8b36263
MD5 a845213ab5ea2796bc7784edd617f5be
BLAKE2b-256 d683c205558b0bf0ca3913eae613bd1ef7934130f1608eac4b92b4b2c62bb0a1

See more details on using hashes here.

Provenance

The following attestation bundles were made for kap_client-1.0.0.tar.gz:

Publisher: release.yml on semudu/kap-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file kap_client-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: kap_client-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 18.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for kap_client-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 779951b82c3db5b20fb1a6836efab15fc50ac770e0e823900d18c8a37455ae52
MD5 f67a1d0fdd0ec209f9ed9a39a0e20f5c
BLAKE2b-256 437e81d9b46c202df624a9ccc5f8ee3113b3892a2c119449625967023a4b2ec2

See more details on using hashes here.

Provenance

The following attestation bundles were made for kap_client-1.0.0-py3-none-any.whl:

Publisher: release.yml on semudu/kap-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page