Blog · Entegrasyon ve mimari

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.

4 dk okuma

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 /v1 isteği kimlik bilgisini X-API-Key başlığında yeniden gönderir.
  • Temsil: İstemci kaynağın kendisini değil, bir temsilini alır. Buradaki yanıtlar application/json; charset=utf-8 tü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:

KaynakUç noktaNot
İllerGET /v1/provincesListenin tamamı tek yanıtta gelir
Bir ilin ilçeleriGET /v1/provinces/{code}/districts{code} il plaka koduyla aynıdır
Bir ilçenin mahalleleriGET /v1/districts/{code}/neighborhoodsListenin tamamı tek yanıtta gelir
Mahallenin cadde/sokaklarıGET /v1/neighborhoods/{code}/streetsCursor ile sayfalı
Sokağın dış kapılarıGET /v1/streets/{code}/buildingsCursor ile sayfalı
Katalog meta verisiGET /v1/metaAktif 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.codeNe yapmalı?
200—Gövdeyi işleyin
400invalid_request, invalid_cursor, invalid_queryİsteği düzeltin; tekrar denemek işe yaramaz
401invalid_api_keyAnahtarı kontrol edin
403insufficient_scope, subscription_inactiveYetkiyi veya paket durumunu kontrol edin
404dataset_not_foundİstenen sürüm veya kayıt yok
429rate_limit_exceeded, quota_exceededBekleyin; körlemesine tekrar denemeyin
503catalog_unavailableGeç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

  1. Kaynakları stableId ile saklayın; adlarla eşleştirme yapmayın.
  2. Okuma için GET, yapılandırılmış gövde için POST kullanın.
  3. Hata yönetimini HTTP durumu ve error.code üzerine kurun.
  4. Doğrulamada HTTP 200 ile valid: false durumunu ayrı ele alın.
  5. Sayfalı uçlarda nextCursor değeri null olana kadar devam edin.
  6. API sürümünü (/v1) ve veri seti sürümünü birbirinden ayırın.
  7. X-API-Key değ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.