Blog · Adres verisi ve rehberler
İlçeye Göre Mahalle Listesi, Mahalleye Göre Sokaklar
İlçeye göre mahalle listesi, mahalleye göre sokak listesi ve dış kapı numaraları API ile nasıl alınır? Cursor sayfalama, q araması ve kod örnekleri.
Adres formu kuran ya da adres verisini kendi sisteminize aktaran bir geliştiriciyseniz, akış genellikle aynıdır: kullanıcı ilçeyi seçer, ardından mahalle listesi gelir. Mahalle seçilince sokaklar, sokak seçilince kapı numaraları listelenir. Bu yazıda her adım için hangi ucu çağıracağınızı, hangi listede sayfalama olduğunu ve kapı numarası aramasını nasıl yapacağınızı çalışır örneklerle anlatıyoruz.
Adres hiyerarşisinin genel yapısını merak ediyorsanız önce Türkiye adres verileri yapısı yazısına göz atabilirsiniz. Burada doğrudan uçlara odaklanıyoruz.
Hangi uç ne döner?
Dört liste ucu var ve sayfalama davranışları farklı:
| Uç | Döndürdüğü | Sayfalama |
|---|---|---|
GET /v1/provinces/{code}/districts | İlin ilçeleri | Yok, tamamı tek yanıtta |
GET /v1/districts/{code}/neighborhoods | İlçenin mahalleleri | Yok, tamamı tek yanıtta |
GET /v1/neighborhoods/{code}/streets | Mahallenin cadde/sokakları | limit + cursor |
GET /v1/streets/{code}/buildings | Sokağın dış kapıları | limit + cursor, ayrıca q |
Tüm isteklerde kimlik doğrulama X-API-Key başlığıyla yapılır. Anahtar tarayıcı koduna konmaz. Frontend kendi backend'inize istek atar, backend de API'yi çağırır. Örneklerdeki kodlar sözleşmeyi göstermek içindir. Uygulamanızda bir üst listeden aldığınız gerçek code değerlerini kullanın.
İlçeye göre mahalle listesi: tamamı tek yanıtta
Mahalle sorgulama için ilçe kodunu yol parametresi olarak verirsiniz. Bu uç, ilçenin tüm mahalle kayıtlarını tek yanıtta döner. limit, cursor veya meta.nextCursor kullanılmaz.
curl "https://api.apiturkiye.com/v1/districts/1352/neighborhoods" \
-H "X-API-Key: apt_live_..."
import os
import requests
BASE = "https://api.apiturkiye.com"
HEADERS = {"X-API-Key": os.environ["APITURKIYE_KEY"]}
res = requests.get(f"{BASE}/v1/districts/1352/neighborhoods", headers=HEADERS, timeout=10)
res.raise_for_status()
neighborhoods = res.json()["data"]
Burada sık yapılan bir hata var. Diğer listelerdeki sayfa boyutu alışkanlığıyla mahalle listesini istemci tarafında ilk 100 kayıtla sınırlamayın. Kalabalık bir ilçede listenin sonundaki mahalleler seçim kutusunda hiç görünmez ve kullanıcı adresini giremez. Sorun da çoğu zaman yalnız belirli ilçelerde ortaya çıktığı için test sırasında fark edilmeyebilir.
Dikkat edilecek iki nokta daha var:
- İlçe listesi yalnız belediye merkezinin mahallelerini değil, ilçeye bağlı diğer yerleşimlerin mahalle kayıtlarını da kapsar. Kayıt sayısını, merkezdeki mahalle sayısı gibi yorumlamayın.
- Aynı mahalle adı farklı yerleşimlerde bulunabilir. Kayıtları ada göre birleştirmeyin veya tekilleştirmeyin. Kimlik olarak
stableIdya dacodekullanın.
Yerleşim bağlamını etikette göstermek
Mahalle liste yanıtındaki her kayıt, stableId, code ve name alanlarına ek olarak bağlı olduğu yerleşimin bilgisini locality alanında taşır:
{
"stableId": "apt_v1_ngh_m8ZIfn90bcFrJdALHcxB7Q",
"code": "34629",
"name": "DEMİROLUK",
"locality": {
"town": { "code": "621", "name": "MERKEZ" },
"village": { "code": "14345", "name": "ÇAVUŞOĞLU" }
}
}
Aynı adlı iki mahalle seçim kutusunda yan yana görünürse kullanıcı hangisini seçeceğini bilemez. locality.village.name değerini etikete eklemek bu belirsizliği giderir:
function toNeighborhoodOptions(list) {
return list.map((n) => {
const place = n.locality?.village?.name;
return {
value: n.stableId,
code: n.code,
label: place ? `${n.name} (${place})` : n.name,
};
});
}
Bu yöntemle seçim kutusunda DEMİROLUK (ÇAVUŞOĞLU) gibi bir etiket görünür. village grubunun adı MERKEZ ya da bir belde adı da olabilir. Bu alan resmî bir köy/belde/şehir sınıflandırması değildir. Onu yalnız ayırt edici bir etiket olarak kullanın, iş kurallarınızı bu alana bağlamayın. locality alanının yalnız mahalle liste ucunda bulunduğunu da unutmayın. Autocomplete bu alanı döndürmez.
Seçimi kaydederken referans olarak stableId değerini saklayın. Bir sonraki adımda sokakları çekmek içinse code değerini kullanın.
Mahalleye göre sokak listesi: cursor ile sayfalama
Sokak listesi uzun olabileceği için bu uç sayfalı çalışır. limit en fazla 100 kayıt alır, varsayılan değeri 50'dir. Yüksek OFFSET desteklenmez. Sonraki sayfayı almak için yanıttaki meta.nextCursor değerini cursor parametresiyle geri gönderirsiniz. nextCursor değeri null ise son sayfadasınız demektir.
curl "https://api.apiturkiye.com/v1/neighborhoods/20239/streets?limit=50" \
-H "X-API-Key: apt_live_..."
{
"data": [ { "stableId": "apt_v1_str_...", "code": "173405", "name": "Moda Caddesi" } ],
"meta": { "nextCursor": "eyJuYW1lLi4u" }
}
Tüm sokakları çekmeniz gerekiyorsa, örneğin bir seçim kutusunu doldurmak için, JavaScript tarafında bir async generator işinizi kolaylaştırır:
async function* allStreets(neighborhoodCode, apiKey) {
let cursor = null;
do {
const url = new URL(`https://api.apiturkiye.com/v1/neighborhoods/${neighborhoodCode}/streets`);
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, { headers: { "X-API-Key": apiKey } });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const body = await res.json();
yield* body.data;
cursor = body.meta.nextCursor;
} while (cursor);
}
const streets = [];
for await (const s of allStreets("20239", process.env.APITURKIYE_KEY)) streets.push(s);
Aynı mantığın Python karşılığı:
def all_streets(neighborhood_code):
params = {"limit": 100}
while True:
res = requests.get(
f"{BASE}/v1/neighborhoods/{neighborhood_code}/streets",
headers=HEADERS, params=params, timeout=10,
)
res.raise_for_status()
body = res.json()
yield from body["data"]
cursor = body["meta"]["nextCursor"]
if not cursor:
break
params["cursor"] = cursor
Cursor değerini olduğu gibi geri gönderin. İçeriğini çözümlemeye ya da kendiniz üretmeye çalışmayın. Geçersiz bir cursor 400 invalid_cursor döner.
Kullanıcının uzun bir listeyi kaydırması yerine sokak adını yazarak bulmasını istiyorsanız GET /v1/autocomplete ucunu types=street ve neighborhoodCode ile daraltarak kullanabilirsiniz. Bu uç cursor kullanmaz, en fazla 20 öneri döner.
Sokaktaki dış kapı numarası listesi ve q ile arama
Sokak seçildikten sonra dış kapılar GET /v1/streets/{code}/buildings ile alınır. Bu uç da limit ve cursor ile sayfalanır:
curl "https://api.apiturkiye.com/v1/streets/173405/buildings?limit=50" \
-H "X-API-Key: apt_live_..."
Uzun bir caddede yüzlerce kapı olabilir. Kullanıcının tüm listeyi kaydırması yerine numarayı yazmasını istiyorsanız q parametresini kullanın. q, dış kapı numarasının başıyla eşleşen kayıtları döner. Örneğin 24 yazıldığında 24 ile başlayan kapı numaraları gelir. Arama aynı sokak içinde kalır ve cursor sayfalamasıyla birlikte çalışır:
curl "https://api.apiturkiye.com/v1/streets/173405/buildings?q=24&limit=50" \
-H "X-API-Key: apt_live_..."
{
"data": [ { "stableId": "apt_v1_dor_...", "id": "88214", "code": "72503885", "doorNumber": "24", "label": "" } ],
"meta": { "nextCursor": null }
}
Backend'inizde bu aramayı saran küçük bir fonksiyon, frontend'deki kapı numarası alanını beslemeye yeter:
async function searchDoors(streetCode, doorPrefix, apiKey, cursor = null) {
const url = new URL(`https://api.apiturkiye.com/v1/streets/${streetCode}/buildings`);
url.searchParams.set("q", doorPrefix);
url.searchParams.set("limit", "50");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, { headers: { "X-API-Key": apiKey } });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const body = await res.json();
return { doors: body.data, nextCursor: body.meta.nextCursor };
}
Kapı kayıtlarında üç kimlik alanı bulunur, hangisini nerede kullanacağınızı ayırt edin:
stableId: sürümden bağımsız kalıcı kimliktir. Veritabanınızda referans olarak bunu saklayın.id: aktif katalog sürümüne ait kayıttır ve sürümler arasında değişebilir. Adres doğrulama ucunda aynı kapı numarasındaki kayıtları ayırmak içindoorIdolarak gönderilebilir.code: kaynak kodudur, eşleştirme için korunur.
Kapı araması autocomplete ucunda yapılmaz. Kapı numarasını her zaman seçilen sokağın /buildings?q=... ucunda arayın.
Hata durumları ve sağlam bir akış
Zincirleme seçimlerde bir adımın hatası sonraki adımları da etkiler. Kararlarınızı error.message metnine göre değil, HTTP durumuna ve error.code değerine göre verin:
400 invalid_cursorveyainvalid_code: cursor ya da kod bozulmuştur. Listeyi cursor'sız baştan isteyin, kodun bir üst listeden geldiğini kontrol edin.401/403: anahtar, paket veya yetki sorunudur. Bu hatalarda otomatik yeniden deneme yapmayın.429 rate_limit_exceeded:Retry-Afterbaşlığındaki süre kadar bekleyip tekrar deneyin.429 quota_exceededgeldiğinde ise aynı çağrıyı tekrarlamak yerine yanıttakiresetAtzamanını bekleyin.503 catalog_unavailable: katalog geçici olarak erişilemez durumdadır. Kullanıcıya anlaşılır bir mesaj gösterin.
Aynı ilçe veya mahalle için tekrar tekrar istek atmamak adına yanıtları sunucu tarafında önbelleğe almayı da düşünebilirsiniz.
Kısa kontrol listesi
- Mahalle listesini tek istekle alın, istemcide kırpmayın.
- Mahalleleri ada göre birleştirmeyin; etikette
locality.village.nameile bağlam gösterin. - Sokak ve kapı listelerinde
meta.nextCursordeğerinullolana kadar ilerleyin. - Kapı numarasını
/buildings?q=...ile, seçili sokak kapsamında arayın. - Referans olarak
stableIdsaklayın, alt listeleri çekerkencodekullanın. - API anahtarını yalnız sunucuda tutun.
Bu adımları bir form arayüzüne dönüştürmek isterseniz il, ilçe ve mahalle seçimli adres formu yazımız ve hazır adres seçici bileşen örnekleri iyi bir başlangıç olur.
Sonraki adım
Uçların tüm parametrelerini ve hata kodlarını API dokümantasyonu sayfasında bulabilirsiniz. Kendi verinizle denemek için ücretsiz kayıt olup API anahtarınızı alabilir, yukarıdaki örnekleri doğrudan çalıştırabilirsiniz.