Skip to content

Quickstart — statistical data

This walkthrough mirrors the tuikr README: start from the theme catalog, drill down to a table, and pull the observations behind it. Every code block is runnable as-is; output shapes are described rather than pasted, because TUIK's catalog changes between releases and a stale printout would mislead you.

1. List the themes

import pytuik

themes = pytuik.get_themes()

Columns: theme_name, theme_id. This is the same catalog the portal's landing page shows, and the first call also performs the cookie session dance the API requires — subsequent calls reuse the session.

Turkish labels come from the same tree:

pytuik.get_themes(lang="tr")

2. Look at one theme's resources

resources = pytuik.get_resources(theme=101)

Columns: theme_name, theme_id, resource_name, resource_type, dataflow_id, resource_url.

resource_type is one of five kinds, taken from the portal's own icon field:

Type Meaning
dataflow An SDMX dataflow you can pull with get_data
istab A static file download (XLS/CSV)
database A link into the legacy biruni interactive database
press A press release
report A published report

Filter at the source when you only want some of them:

pytuik.get_resources(theme=101, types=["dataflow", "istab"])

Bad theme IDs are helpful, not cryptic

Passing an unknown theme raises TuikValidationError listing every valid id = name pair — the behavior tuikr users rely on.

3. Narrow to tables and databases

Two convenience filters wrap get_resources:

tables = pytuik.get_tables(theme=101)      # dataflow + istab
dbs = pytuik.get_databases(theme=101)      # legacy database links

get_tables columns: theme_name, theme_id, table_name, node_type, dataflow_id, table_url. get_databases columns: theme_name, theme_id, db_name, db_url.

Rows whose node_type is dataflow carry a dataflow_id such as TR,DF_ADNKS_T26,1.0 — agency, flow, version. That triple is the key to the SDMX service; istab rows have <NA> there and are downloaded from table_url directly.

4. Fetch the observations

The SDMX service is currently gated

As of 2026-08-10 nsiws.tuik.gov.tr answers every anonymous request with 401 Unauthorized, so the calls in this section raise TuikSDMXError. See known limitations. Everything below describes the contract pytuik implements and is unchanged by that outage.

df = pytuik.get_data("TR,DF_ADNKS_T26,1.0", start="2020", end="2023")

You get a tidy long frame: the surviving dimensions first, each optionally followed by its {DIM}_label column, then obsTime and obsValue.

Three things happen to the raw SDMX response, ported verbatim from tuikr:

  1. every string cell is whitespace-trimmed;
  2. invariant dimensions are dropped — a dimension with one distinct value carries no information in this slice, so the column goes;
  3. for each surviving coded dimension, a {DIM}_label column is inserted right after it — but only when the labels actually differ from the codes.

obsValue is a float; every other column uses pandas' string dtype. The obsTime/obsValue names are kept verbatim (not snake_cased) to match both tuikr and the wider SDMX ecosystem.

Turn off what you do not need

# Skip the second HTTP call that fetches labels — halves requests for bulk pulls.
pytuik.get_data("TR,DF_ADNKS_T26,1.0", labels=False)

# Keep every dimension, even the constant ones.
pytuik.get_data("TR,DF_ADNKS_T26,1.0", drop_invariant=False)

# Turkish labels.
pytuik.get_data("TR,DF_ADNKS_T26,1.0", lang="tr")

5. When key="ALL" is rejected

Some dataflows are too large for ALL, or reject it outright. You then need a narrower SDMX key — a dot-separated path with one slot per dimension, in key order. Ask the structure for that order:

structure = pytuik.get_data_structure("TR,DF_ADNKS_T26,1.0")

structure.dimensions      # ['REF_AREA', 'SEX', ...] — key order
structure.time_dimension  # 'TIME_PERIOD'
structure.label_maps["SEX"]   # {'T': 'Total', 'M': 'Male', ...}

Build the key by filling the slots you want to constrain and leaving the rest empty:

# one slot per non-time dimension: constrain the first, leave the rest open
slots = ["" for _ in structure.dimensions]
slots[0] = "TR"
key = ".".join(slots)          # e.g. 'TR..' for three dimensions

pytuik.get_data("TR,DF_ADNKS_T26,1.0", key=key)

structure.dimensions lists the non-time dimensions only, so its length is exactly the number of slots the key needs.

This is a deliberate improvement over tuikr

tuikr keeps the structure internal, which leaves users guessing at keys. get_data_structure is public here for exactly this reason.

6. Reuse one client

The module-level functions delegate to a lazily created default client. For scripts that make many calls — or that need different timeouts, retries, or a Turkish default — construct your own:

from pytuik import TuikClient

with TuikClient(lang="tr", timeout=60.0, retries=5, cache_ttl=3600) as client:
    themes = client.get_themes()
    df = client.get_data("TR,DF_ADNKS_T26,1.0", start="2020")

Every module-level function exists as a method with the same signature. To reconfigure the process-wide default instead:

pytuik.set_default_client(TuikClient(lang="tr"))

The theme tree and the geographic catalog are cached in memory for cache_ttl seconds (one hour by default; cache_ttl=0 disables it, client.clear_cache() empties it).

7. Handle failures

from pytuik import (
    TuikError,            # base class for everything below
    TuikHTTPError,        # non-2xx after retries; carries status + URL
    TuikPortalError,      # the portal envelope came back isError=True
    TuikSDMXError,        # SDMX service or parse failure (e.g. key rejected)
    TuikValidationError,  # bad arguments — raised before any network I/O
)

try:
    df = pytuik.get_data("TR,DF_SOMETHING,1.0")
except TuikSDMXError as exc:
    print("upstream said no:", exc)

TuikValidationError also subclasses ValueError, so callers that never imported pytuik's exceptions still catch it naturally.

8. Be a good citizen

TUIK publishes no rate limit and no API contract. Requests are made one at a time with a browser-like User-Agent; keep it that way — no parallel hammering, and add a courtesy delay in bulk loops:

import time

for flow in flows:
    frames.append(pytuik.get_data(flow, labels=False))
    time.sleep(1)