Blog · Adres verisi ve rehberler
Türkiye İl İlçe Listesi: Güncel Veriyi JSON Olarak Alın
Türkiye il ilçe listesini statik dosya yerine API'den JSON olarak alın. stableId ve code alanlarını kullanın, listeyi sürüm değişince yenilenen önbellekte tutun.
Bir projeye Türkiye il ilçe listesi eklemek gerektiğinde çoğu ekip bir GitHub deposundan JSON dosyası indirir ya da eski bir projeden SQL dökümünü kopyalar. İlk gün bu yeterli görünür. Sonraki aylarda sorunlar başlar: listenin ne zaman güncellendiği belli değildir, kayıtların kalıcı bir kimliği yoktur ve listeyi yenilemek için bir yöntem tanımlanmamıştır. Bu yazıda il ve ilçe verisini API'den JSON olarak almayı anlatıyoruz. Ardından kayıtları hangi alanla saklamanız gerektiğini ve listeyi katalog sürümü değiştiğinde yenilenen bir önbellekte nasıl tutacağınızı gösteriyoruz.
Statik il ilçe listesi neden eskir?
Statik bir dosya, kopyalandığı andaki durumu yansıtır. İdari yapıda değişiklik olduğunda dosya kendiliğinden güncellenmez. Dosyayı kopyalayan her proje ayrı bir sürüm taşımaya başlar. Bu da tipik olarak şu sorunlara yol açar:
- Kaynak belirsizliği: Dosyanın hangi tarihte ve hangi kaynaktan üretildiğini çoğu zaman kimse bilmez.
- Kimlik karmaşası: Dökümlerde genellikle otomatik artan bir
idbulunur. Başka bir dökümle birleştirdiğinizde aynı ilçe farklı numaralarla karşınıza çıkabilir. - Ad üzerinden eşleştirme: Kimlik güvenilir olmadığında ekipler kayıtları ada göre eşleştirir. Yazım farkları, büyük/küçük harf ve Türkçe karakter sorunları bu noktada başlar.
- Dağınık güncelleme: Mobil uygulama, web sitesi ve arka ofis farklı dosyalar kullanır. Aynı müşteri her ekranda farklı bir ilçe listesiyle karşılaşabilir.
API'den alınan bir il ilçe listesi bu sorunları ortadan kaldırmaz ama yönetilebilir hâle getirir. Tek bir kaynağınız, her kayıtta kalıcı bir kimliğiniz ve ne zaman yenilemeniz gerektiğini söyleyen bir sürüm bilginiz olur.
Türkiye il listesi: /v1/provinces yanıtı
Katalogda 81 il ve 973 ilçe bulunur. İl listesini almak için tek bir istek yeterlidir. Kimlik doğrulama her istekte X-API-Key başlığıyla yapılır.
curl "https://api.apiturkiye.com/v1/provinces" -H "X-API-Key: apt_live_..."
Yanıt bir data dizisi döner. Her kayıtta üç alan bulunur:
{ "data": [ { "stableId": "apt_v1_prv_...", "code": 1, "name": "Adana" } ] }
İl listesi tek yanıtta tam olarak gelir. Bu uçta limit, cursor veya meta.nextCursor kullanılmaz, bu yüzden sayfalama döngüsü yazmanız gerekmez.
İllerde code sayısaldır ve resmî araç plaka koduyla aynıdır. Adana için 1, İstanbul için 34 gelir. Arayüzde "01" biçiminde göstermek isterseniz bunu kendiniz biçimlendirin: String(code).padStart(2, "0"). Plaka kodlarının sistemlerde nasıl kullanılacağını il plaka kodları yazımızda ayrıntılı olarak ele aldık.
İlçeler: /v1/provinces/{code}/districts
Bir ilin ilçelerini almak için ilin code değerini yola yazarsınız. Örneğin İstanbul için:
curl "https://api.apiturkiye.com/v1/provinces/34/districts" -H "X-API-Key: apt_live_..."
İlçe listesi de seçilen ilin tüm ilçelerini tek yanıtta döner ve sayfalama kullanmaz. Kayıtlar yine stableId, code ve name alanlarını taşır. Bir farka dikkat edin: illerde code sayısaldır, ilçelerde ise metindir. Veritabanı şemanızı ve tip tanımlarınızı buna göre kurun. İlçe kodunu sayıya çevirip saklamayın.
Python ile aynı istek şöyle yapılır:
import os, requests
res = requests.get(
"https://api.apiturkiye.com/v1/provinces/34/districts",
headers={"X-API-Key": os.environ["APITURKIYE_KEY"]},
timeout=10,
)
res.raise_for_status()
districts = res.json()["data"]
for d in districts:
print(d["code"], d["name"])
Yalnızca ilçe adı göstermek size yetmeyebilir. Kullanıcının adresini seçtiği bir form kuruyorsanız il, ilçe ve mahalle seçimini birbirine bağlamanız gerekir. Bunun için il, ilçe ve mahalle seçimli adres formu yazısındaki adımları izleyebilirsiniz.
stableId ve code: hangisini saklamalı?
Her kayıt iki kimlik taşır. İkisinin görevi farklıdır.
| Alan | Ne işe yarar | Nerede kullanılır |
|---|---|---|
stableId | Kalıcı ve sürümden bağımsız kimlik | Veritabanında referans olarak saklanır |
code | Kaynak kodu (il için sayısal, ilçe için metin) | API yollarında ve eşleştirmede kullanılır |
Pratik kural şöyledir: Müşteri adresi, şube veya teslimat bölgesi gibi kalıcı bir kayıtta ilçeye referans veriyorsanız stableId saklayın. code değerini de yanında tutmanız faydalıdır, çünkü /v1/provinces/{code}/districts gibi uçlar yolda code bekler.
Adı yalnız görüntüleme için kullanın. Adı birincil anahtar ya da eşleştirme ölçütü olarak kullanmayın.
Basit bir tablo tasarımı şöyle olabilir:
CREATE TABLE customer_address (
id BIGSERIAL PRIMARY KEY,
province_stable_id TEXT NOT NULL,
province_code SMALLINT NOT NULL,
district_stable_id TEXT NOT NULL,
district_code TEXT NOT NULL,
district_name_snapshot TEXT -- yalnız görüntüleme/geçmiş için
);
Türkiye il ilçe listesini önbelleğe almak ve sürüm değişince yenilemek
İl ve ilçe verisi sık değişmez. Her sayfa açılışında API'ye gitmek gereksiz istek ve gecikme demektir, ayrıca paket kotanızı boşuna harcar. Önerilen yaklaşım şudur:
- Listeyi sunucu tarafında önbelleğe alın.
- Belirli aralıklarla aktif katalog sürümünü kontrol edin.
- Sürüm değiştiyse önbelleği boşaltıp listeyi yeniden çekin.
Sürüm bilgisi için GET /v1/datasets/current ucunu kullanabilirsiniz. Bu uç data.datasetVersion alanında UUID biçiminde bir sürüm kimliği döner. Ancak datasets:read yetkisi gerektirir ve bu yetkiyi paketiniz belirler. Anahtarınızda bu yetki yoksa API 403 insufficient_scope döner. Bu durumda zaman bazlı yenilemeye geçmek makul bir yedek yöntemdir.
Node.js örneği
Aşağıdaki kod Node.js 18 ve üzerinde, ek paket gerektirmeden çalışır:
const BASE = "https://api.apiturkiye.com";
const HEADERS = { "X-API-Key": process.env.APITURKIYE_KEY };
const CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000; // ihtiyacınıza göre ayarlayın
let cache = { version: null, checkedAt: 0, provinces: null, districts: new Map() };
async function getJson(path) {
const res = await fetch(`${BASE}${path}`, { headers: HEADERS });
const body = await res.json().catch(() => ({}));
if (!res.ok) {
const err = new Error(body.error?.message ?? `HTTP ${res.status}`);
err.status = res.status;
err.code = body.error?.code;
throw err;
}
return body;
}
async function currentVersion() {
try {
const { data } = await getJson("/v1/datasets/current");
return data.datasetVersion;
} catch (err) {
if (err.code === "insufficient_scope") return null; // zaman bazlı yenileme
throw err;
}
}
async function ensureFresh() {
if (cache.provinces && Date.now() - cache.checkedAt < CHECK_INTERVAL_MS) return;
try {
const version = await currentVersion();
if (!cache.provinces || version === null || version !== cache.version) {
const { data } = await getJson("/v1/provinces");
cache = { version, checkedAt: Date.now(), provinces: data, districts: new Map() };
} else {
cache.checkedAt = Date.now();
}
} catch (err) {
if (!cache.provinces) throw err; // ilk yükleme başarısızsa hatayı ilet
console.error("Katalog kontrolü başarısız:", err.status, err.code);
cache.checkedAt = Date.now(); // eldeki listeyle devam et
}
}
export async function getProvinces() {
await ensureFresh();
return cache.provinces;
}
export async function getDistricts(provinceCode) {
await ensureFresh();
const key = String(Number(provinceCode));
if (!cache.districts.has(key)) {
const { data } = await getJson(`/v1/provinces/${key}/districts`);
cache.districts.set(key, data);
}
return cache.districts.get(key);
}
İlçeler tembel (lazy) yüklenir. Bir ilin ilçeleri ilk kez istendiğinde API'den çekilir, sonraki isteklerde önbellekten gelir. Sürüm değiştiğinde ilçe önbelleği de sıfırlanır.
Birden fazla sunucu örneği çalıştırıyorsanız bellek içi önbellek yerine Redis gibi ortak bir depo kullanın. Anahtara sürümü ekleyin (ör. apt:districts:{datasetVersion}:34). Böylece eski sürüme ait kayıtlar yeni sürümle karışmaz.
Python örneği
import os, time, requests
BASE = "https://api.apiturkiye.com"
HEADERS = {"X-API-Key": os.environ["APITURKIYE_KEY"]}
CHECK_INTERVAL = 6 * 60 * 60
_cache = {"version": None, "checked_at": 0.0, "provinces": None, "districts": {}}
def _get(path):
res = requests.get(f"{BASE}{path}", headers=HEADERS, timeout=10)
res.raise_for_status()
return res.json()
def _current_version():
res = requests.get(f"{BASE}/v1/datasets/current", headers=HEADERS, timeout=10)
if res.status_code == 403 and res.json().get("error", {}).get("code") == "insufficient_scope":
return None
res.raise_for_status()
return res.json()["data"]["datasetVersion"]
def ensure_fresh():
if _cache["provinces"] and time.time() - _cache["checked_at"] < CHECK_INTERVAL:
return
version = _current_version()
if not _cache["provinces"] or version is None or version != _cache["version"]:
_cache.update(version=version, provinces=_get("/v1/provinces")["data"], districts={})
_cache["checked_at"] = time.time()
def get_districts(province_code):
ensure_fresh()
key = str(int(province_code))
if key not in _cache["districts"]:
_cache["districts"][key] = _get(f"/v1/provinces/{key}/districts")["data"]
return _cache["districts"][key]
Hata durumlarında önbelleği doğru kullanmak
Önbellek, API'ye geçici olarak ulaşılamadığında da işe yarar. Kararlarınızı hata mesajının metnine göre değil, HTTP durum kodu ve error.code değerine göre verin:
503 catalog_unavailable: Eldeki listeyi sunmaya devam edin, bir sonraki kontrolde yeniden deneyin.429 rate_limit_exceeded:Retry-Afterbaşlığındaki süre kadar bekleyin.429 quota_exceeded: YanıttakiresetAtzamanına kadar aynı çağrıyı tekrarlamayın.401veya403: Anahtarınızı, paketinizi ya da yetkilerinizi düzeltmeden otomatik yeniden deneme yapmayın. Bu durumları izleme sisteminize uyarı olarak gönderin.
Frontend tarafında il ilçe json kullanımı
API anahtarı tarayıcı koduna konmaz. Frontend uygulamanız il ilçe JSON verisini doğrudan API'den değil, kendi backend'inizdeki bir uçtan almalıdır. Örneğin /api/provinces ve /api/provinces/:code/districts gibi iki uç tanımlayıp yukarıdaki getProvinces ve getDistricts fonksiyonlarını çağırabilirsiniz.
Bu düzenin iki yararı vardır. Anahtar sunucuda kalır ve tüm istemciler aynı önbellekten beslenir. İsterseniz bu uçların yanıtlarına kısa süreli Cache-Control başlıkları ekleyerek tarayıcı tarafında da tekrar eden istekleri azaltabilirsiniz. Hazır seçim kutusu örnekleri için adres seçici bileşenler sayfasına bakabilirsiniz.
Kısa kontrol listesi
- İl listesini
/v1/provinces, ilçeleri/v1/provinces/{code}/districtsucundan alın. İkisi de sayfalama kullanmaz. - Kalıcı kayıtlarda
stableIdsaklayın. API yolları içincodedeğerini de tutun. - İl
codedeğeri sayısaldır, ilçecodedeğeri metindir. - Listeyi sunucuda önbelleğe alın ve
datasetVersiondeğiştiğinde yenileyin. - Sürüm bilgisine erişiminiz yoksa zaman bazlı yenileme uygulayın.
- API anahtarını tarayıcıya, Git deposuna ve loglara koymayın.
Uç ayrıntılarına ve hata kodlarının tamamına API dokümantasyonundan ulaşabilirsiniz. Denemeye başlamak için ücretsiz kayıt olup API anahtarınızı alabilir, paket kapsamlarını Türkiye adres API'si paketleri bölümünde karşılaştırabilirsiniz.