REST API Nedir? Adres API'si ile Temel Kavramlar
REST API nedir? Kaynak, uç nokta, HTTP metodları, durum kodları, JSON, sayfalama ve sürümlemeyi gerçek bir adres API'si üzerinden örneklerle öğrenin.
REST API nedir sorusunun kısa yanıtı şudur: Verileri adreslenebilir kaynaklar hâlinde HTTP üzerinden sunan bir arayüz tasarım tarzıdır. İstemci bu arayüzle standart metodlar, durum kodları ve çoğunlukla JSON biçimindeki gövdeler aracılığıyla konuşur. Tanım kâğıt üzerinde soyut kalabilir. Bu yazı aynı kavramları gerçek bir adres API'sinin uçları üzerinden, kopyalanabilir örneklerle ele alıyor. API kavramına yeniyseniz önce API nedir yazısına göz atabilirsiniz. Burada doğrudan REST'in yapı taşlarına odaklanılıyor.
REST API nedir ve hangi ilkelere dayanır?
REST bir protokol ya da kütüphane değildir; HTTP'nin zaten sunduğu araçları tutarlı biçimde kullanmayı öneren bir mimari tarzdır. Günlük işte karşılaşacağınız ilkeler şunlardır:
- Kaynak odaklılık: Sistemdeki her anlamlı şey, örneğin bir il ya da bir sokak, URL ile adreslenebilen bir kaynaktır.
- Tekdüze arayüz: Okuma, gönderme ve hata bildirme işleri her uçta aynı HTTP metodları ve durum kodlarıyla yapılır.
- Durumsuzluk: Her istek, anlaşılması için gereken her şeyi kendisi taşır. Sunucu, önceki isteği hatırlamak için oturum tutmaz. API Türkiye'de bu yüzden her
/v1isteği kimlik bilgisiniX-API-Keybaşlığında yeniden gönderir. - Temsil: İstemci kaynağın kendisini değil, bir temsilini alır. Buradaki yanıtlar
application/json; charset=utf-8türündedir.
Kaynak ve uç nokta: aynı şey değil
Kaynak bir kavramdır: "İstanbul'un ilçeleri" gibi. Uç nokta ise bu kavrama ulaşmak için kullandığınız metod ve yol ikilisidir. Türkiye adres API'si il → ilçe → mahalle → cadde/sokak → dış kapı hiyerarşisini doğrudan URL yapısına yansıtır:
| Kaynak | Uç nokta | Not |
|---|---|---|
| İller | GET /v1/provinces | Listenin tamamı tek yanıtta gelir |
| Bir ilin ilçeleri | GET /v1/provinces/{code}/districts | {code} il plaka koduyla aynıdır |
| Bir ilçenin mahalleleri | GET /v1/districts/{code}/neighborhoods | Listenin tamamı tek yanıtta gelir |
| Mahallenin cadde/sokakları | GET /v1/neighborhoods/{code}/streets | Cursor ile sayfalı |
| Sokağın dış kapıları | GET /v1/streets/{code}/buildings | Cursor ile sayfalı |
| Katalog meta verisi | GET /v1/meta | Aktif katalog bilgisi |
Burada iki tasarım kararını görebilirsiniz. Yol parametreleri ({code}) hangi kaynağın istendiğini belirtir ve hiyerarşiyi okunur kılar. Sorgu parametreleri (limit, cursor, q) ise aynı kaynağın nasıl döneceğini şekillendirir.
Kaynakları kendi sisteminizde saklarken hangi alanı tutacağınız da önemlidir. Her kayıtta sürümden bağımsız bir stableId bulunur ve referans olarak bunu saklamanız önerilir. code alanı eşleştirme için korunur. Ad alanı ise kimlik değildir, çünkü aynı mahalle adı farklı yerleşimlerde tekrar edebilir.
HTTP metodları: GET ile okumak
HTTP metodları, kaynak üzerinde ne yapmak istediğinizi söyler. GET okuma içindir: sunucuda bir şey değiştirmez ve aynı isteği tekrar göndermek aynı sonucu üretmeye yöneliktir. Adres kataloğundaki liste uçlarının hepsi GET kullanır.
İstanbul'un (plaka kodu 34) ilçelerini üç farklı yolla şöyle alabilirsiniz:
curl "https://api.apiturkiye.com/v1/provinces/34/districts" \
-H "X-API-Key: apt_live_..."
// Node.js (sunucu tarafı); anahtar ortam değişkeninden okunur
const res = await fetch("https://api.apiturkiye.com/v1/provinces/34/districts", {
headers: { "X-API-Key": process.env.APITURKIYE_KEY },
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { data } = await res.json();
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"]
Başarılı yanıtlar bir zarf içinde gelir. Asıl içerik data alanındadır; uca göre ek bilgiler meta alanında bulunur. /v1 yanıtlarında ayrı bir success alanı yoktur. Başarıyı HTTP durum kodundan okursunuz. API anahtarını tarayıcı koduna koymayın; bu istekleri her zaman sunucu tarafında yapın. Liste verisini doğrudan bir açılır menüde kullanmak istiyorsanız il ve ilçe listesini JSON olarak alma yazısı bu adıma odaklanır.
POST ile veri göndermek
POST, isteğin gövdesinde yapılandırılmış veri gönderdiğiniz durumlar için kullanılır. Adres doğrulama ucu iyi bir örnektir. Bu uç bir kaynak oluşturmaz, ancak beş ayrı bileşeni JSON gövdesiyle alır. Bu uç Pro ve üzeri paketlerdeki addresses:validate yetkisini gerektirir.
curl "https://api.apiturkiye.com/v1/addresses/validate" \
-H "X-API-Key: apt_live_..." \
-H "Content-Type: application/json" \
--data '{"province":"İstanbul","district":"Kadıköy","neighborhood":"Caferağa Mahallesi","street":"Moda Caddesi","doorNumber":"12"}'
Dikkat edilmesi gereken bir REST inceliği vardır. Adres eşleşmezse yanıt yine HTTP 200 olur. Gövdede data.valid: false, eşleşmenin koptuğu seviye (failedAt) ve varsa öneriler (suggestions) yer alır. HTTP durumu isteğin işlenip işlenmediğini anlatır. "Adres geçerli mi?" sorusunun yanıtı ise gövdededir. Bozuk veya eksik bir JSON gövdesi ise 400 döner. Bu iki durumu kodunuzda ayrı ele alın.
Durum kodları ve hata gövdesi
Durum kodu, yanıtı okumadan önce ne olduğunu söyleyen ilk sinyaldir. En sık karşılaşacaklarınız şunlardır:
| Durum | Örnek error.code | Ne yapmalı? |
|---|---|---|
200 | — | Gövdeyi işleyin |
400 | invalid_request, invalid_cursor, invalid_query | İsteği düzeltin; tekrar denemek işe yaramaz |
401 | invalid_api_key | Anahtarı kontrol edin |
403 | insufficient_scope, subscription_inactive | Yetkiyi veya paket durumunu kontrol edin |
404 | dataset_not_found | İstenen sürüm veya kayıt yok |
429 | rate_limit_exceeded, quota_exceeded | Bekleyin; körlemesine tekrar denemeyin |
503 | catalog_unavailable | Geçici durum; daha sonra deneyin |
Hata gövdeleri de tutarlı bir yapıdadır:
{ "error": { "code": "invalid_api_key", "message": "Geçerli bir X-API-Key başlığı zorunludur." } }
Kararlarınızı message metnine değil, HTTP durumuna ve error.code değerine göre verin. Mesaj metni insanlar için yazılır ve değişebilir. Kod ise sözleşmenin parçasıdır. Aşağıdaki yardımcı fonksiyon bu yaklaşımı uygular:
import os, time, requests
API = "https://api.apiturkiye.com"
def get_json(path, params=None, attempts=3):
for _ in range(attempts):
res = requests.get(f"{API}{path}", params=params,
headers={"X-API-Key": os.environ["APITURKIYE_KEY"]},
timeout=10)
if res.ok:
return res.json()
err = res.json().get("error", {})
if res.status_code == 429 and err.get("code") == "rate_limit_exceeded":
time.sleep(int(res.headers.get("Retry-After", "1")))
continue
raise RuntimeError(f"{res.status_code} {err.get('code')}")
raise RuntimeError("Yeniden deneme hakkı tükendi")
quota_exceeded durumunda yanıttaki resetAt zamanına kadar beklemek gerekir. 401 ve 403 durumlarında ise anahtar, yetki veya paket düzeltilmeden otomatik tekrar yapılmamalıdır. Fonksiyon bu yüzden yalnız hız sınırında yeniden dener.
Sayfalama: neden offset değil, cursor?
Büyük listeleri tek yanıtta göndermek hem sunucuyu hem istemciyi yorar. Klasik çözüm ?page=40 ya da OFFSET kullanmaktır. Ancak sayfa ilerledikçe maliyet artar ve araya kayıt girerse sayfalar kayabilir. Cursor yaklaşımında sunucu size "kaldığınız yeri" gösteren opak bir işaret verir. Siz de bir sonraki istekte bu işareti geri gönderirsiniz.
Cadde/sokak ve dış kapı uçları limit (en fazla 100) ve cursor kabul eder. Sonraki sayfa için meta.nextCursor değerini kullanırsınız. Bu değer null olduğunda son sayfadasınız demektir:
def all_buildings(street_code):
cursor = None
while True:
params = {"limit": 100}
if cursor:
params["cursor"] = cursor
body = get_json(f"/v1/streets/{street_code}/buildings", params)
yield from body["data"]
cursor = body["meta"]["nextCursor"]
if cursor is None:
break
Cursor değerini çözümlemeye ya da elle üretmeye çalışmayın. Geçersiz bir değer 400 invalid_cursor döner. İl, ilçe ve mahalle listeleri ise kapsamın tamamını tek yanıtta verir ve bu uçlarda sayfalama yoktur. Mahalle listesini istemci tarafında ilk 100 kayıtla kesmemeye özellikle dikkat edin.
Sürümleme: /v1 ne anlatır?
Yoldaki /v1 öneki API sözleşmesinin sürümüdür. Uç adları, parametreler ve yanıt alanları bu sözleşmeye bağlıdır. Yaygın REST uygulamasında geriye dönük uyumu bozan değişiklikler yeni bir önek altında yayınlanır. Böylece mevcut entegrasyonlar habersiz kırılmaz.
Adres verisi söz konusu olduğunda ikinci bir sürüm kavramı da vardır: veri seti sürümü. API sözleşmesi aynı kalırken katalog içeriği güncellenebilir. Aktif veri sürümünü GET /v1/datasets/current ile görebilirsiniz (datasets:read yetkisi gerekir). Doğrulama yanıtı da kullanılan sürümü meta.datasetVersion alanında bildirir. Bu ayrım saklama kararlarınızı da etkiler. Dış kapıların id alanı sürümler arasında değişebilir, stableId ise değişmez.
Kısa kontrol listesi
- Kaynakları
stableIdile saklayın; adlarla eşleştirme yapmayın. - Okuma için
GET, yapılandırılmış gövde içinPOSTkullanın. - Hata yönetimini HTTP durumu ve
error.codeüzerine kurun. - Doğrulamada HTTP
200ilevalid: falsedurumunu ayrı ele alın. - Sayfalı uçlarda
nextCursordeğerinullolana kadar devam edin. - API sürümünü (
/v1) ve veri seti sürümünü birbirinden ayırın. X-API-Keydeğerini yalnız sunucuda tutun; loglara ve depolara yazmayın.
Kavramları denemenin en hızlı yolu, yukarıdaki örnekleri kendi anahtarınızla çalıştırmaktır. Tüm uçların ayrıntıları için API dokümantasyonuna bakabilir, ücretsiz kayıt ile Core paketinde bir API anahtarı alarak başlayabilirsiniz.