Bir API sürümünü emekliye ayırırken istemcilere iki ayrı bilgi verirsiniz: "bu kaynak kullanımdan kalktı veya kalkacak" ve "bu kaynak şu tarihte yanıt vermeyi bırakacak". HTTP'de ilki için RFC 9745 ile standartlaşan Deprecation başlığı (Deprecation: @1796083200), ikincisi için RFC 8594 tanımlı Sunset başlığı (Sunset: Tue, 01 Jun 2027 00:00:00 GMT) kullanılır. Ayrıntılı geçiş belgesine ise Link başlığında rel="deprecation" ve rel="sunset" ilişkileriyle bağlantı verilir.

Bu yazıda iki başlığın sözdizimini, birbirleriyle ilişkisini ve aralarındaki tarih biçimi farkını RFC metinlerine dayanarak açıklıyor, bir FastAPI ara katmanıyla (middleware) v1 yanıtlarına bu başlıkları ekleyip kapanış tarihinden sonra 410 döndüren çalışan bir örnek veriyoruz. Yeni sürümün nasıl açılacağı API versiyonlama yazısında, hangi değişikliğin yeni sürüm gerektirdiği breaking change yazısında anlatılıyor. Serinin tamamı için REST API tasarımı öğrenme rehberine bakın.

Kullanımdan kaldırma iki aşamadır

Kullanımdan kaldırma iki aşamadır: Tanımlayan belge, Anlattığı, Değer biçimi, Geçmiş tarih anlamı

RFC 8594, Web API'lerinde kullanımdan kaldırmanın (deprecation) çoğu zaman iki aşamada gerçekleştiğini anlatır. İlk aşamada API veya belirli bir sürümü artık tercih edilen ya da önerilen sürüm değildir ama çalışmaya devam eder. İkinci aşamada o sürüm hizmetten çekilir (decommission) ve yanıt vermez hâle gelir. İki başlık bu iki aşamaya karşılık gelir:

ÖzellikDeprecationSunset
Tanımlayan belgeRFC 9745 (Mart 2025, standart)RFC 8594 (Mayıs 2019)
AnlattığıKaynak kullanımdan kalktı veya kalkacakKaynak bu tarihte yanıt vermeyebilir
Değer biçimiStructured Field tarihi: @ ve Unix zamanıHTTP-date: Tue, 01 Jun 2027 00:00:00 GMT
Geçmiş tarih anlamıKaynak o tarihte kullanımdan kalktıKaynak her an kapanabilir
Kaynağın davranışıDeğişmez, kaynak çalışmaya devam ederTarihten sonra 4xx, 3xx veya hiç yanıt
Bağlantı ilişkisirel="deprecation": geçiş belgelerirel="sunset": emeklilik politikası

RFC 8594 açıkça, ilk aşama için Sunset başlığının uygun olmadığını söyler; çünkü o aşamada API hâlâ çalışır ve kullanılabilir. Sunset yalnızca gerçekten yanıt vermeyi bırakma anını duyurmak içindir. İlk aşamanın standart sinyali, sonradan yayımlanan RFC 9745 ile gelen Deprecation başlığıdır.

Deprecation başlığı

RFC 9745'e göre Deprecation, yanıtın ait olduğu kaynağın kullanımdan kalkacağını veya kalktığını bildiren bir yanıt başlığıdır. Değeri, Structured Field Values (RFC 9651) biçiminde bir tarihtir: @ işaretinin ardından 1 Ocak 1970 UTC'den bu yana geçen saniye sayısı gelir. RFC'nin kendi örneği Deprecation: @1688169599, yani 30 Haziran 2023 23:59:59 UTC'dir. Tarih gelecekte olabilir (kaynak o tarihte kullanımdan kalkacak) ya da geçmişte (kaynak o tarihte kullanımdan kalktı).

RFC'nin üzerinde durduğu en önemli nokta şudur: kullanımdan kaldırma, kaynağın davranışını değiştirmez. Başlığın yanıtta görünmesi, kaynağın anlamının veya işlevinin değiştiği anlamına gelmez; istemciler kaynağı önceki gibi kullanabilir. Başlık bir ipucudur (hint) ve istemcilerin bu bilgi olmadan da çalışabilecek şekilde yazılması beklenir. Öte yandan RFC, tarih geçmişteyse istemci geliştiricilerinin kaynağın davranışının eskisi gibi kalacağını artık varsaymaması gerektiğini de ekler: işlevsel olmayan ayrıntılar, örneğin yanıt süreleri, etkilenebilir.

Sunset başlığı

RFC 8594'e göre Sunset, kaynağın belirli bir anda yanıt vermez hâle gelmesinin beklendiğini bildirir. Değeri tek bir HTTP-date zaman damgasıdır ve gelecekte olması gerekir. RFC 9110, HTTP-date üretirken Sun, 06 Nov 1994 08:49:37 GMT biçimindeki IMF-fixdate formatının kullanılmasını zorunlu tutar; saat her zaman UTC olarak yorumlanır ve GMT kısaltmasıyla yazılır. Geçmiş bir tarih gelirse, RFC 8594 bunu en güvenli biçimde "şimdi" olarak yorumlamayı, yani kaynağın her an kapanabileceğini düşünmeyi önerir.

Sunset da bir ipucudur: kaynağın o ana kadar mutlaka çalışacağını veya o andan sonra mutlaka kapanacağını garanti etmez. RFC, tarih geçtikten sonra etkileşimlerin büyük olasılıkla istemci hatası (4xx), yönlendirme (3xx) ya da hiç yanıt alınamaması ile sonuçlanacağını, başlığın bunlardan hangisinin olacağını söylemediğini belirtir. Başlığı tanımayan istemciler normal çalışmaya devam eder ve kaynak kapandığında bunu önceden bir bildirim görmeden yaşar. Bu yüzden başlıklar tek başına bir duyuru kanalı değildir; belgelerdeki duyuruyu tamamlarlar.

İki tarih arasındaki kural ve biçim farkı

RFC 9745, iki başlık birlikte kullanıldığında Sunset zaman damgasının Deprecation zaman damgasından önce olamayacağını kesin bir kural olarak koyar. Bir yapılandırma hatası nedeniyle bu sıra bozulursa, istemci geliştiricisinin kaynağın sahibine danışması beklenir. Mantığı açıktır: bir şey önce kullanımdan kalkar, sonra kapanır.

Aynı RFC, tarihsel nedenlerle iki başlığın farklı tarih biçimi kullandığını not eder. Bu, uygulamada en sık hata yapılan noktadır: Deprecation değerine HTTP-date ya da ISO 8601 metni yazmak veya Sunset değerine @ ile başlayan Unix zamanı yazmak, başlığı tanıyan istemcilerin onu ayrıştıramamasına yol açar. Tabloda gördüğünüz iki biçimi birbirine karıştırmayın; tarihleri kodda tek bir yerde tutup her başlık için doğru biçime çeviren küçük bir yardımcı yazmak bu hatayı önler.

Başlığın kapsamı

İki başlık da varsayılan olarak yalnızca yanıtın ait olduğu kaynak için geçerlidir. Bir sürümün tamamını emekliye ayırırken bu, v1 altındaki her yanıta başlıkları eklemek anlamına gelir. RFC'ler kapsamın genişletilmesine de izin verir: örneğin API yalnızca giriş belgesinde (home document) başlığı gösterip bunun tüm API için geçerli olduğunu belgeleyebilir. Ancak RFC'ler, bu genişletilmiş kapsamın kuralı bilmeyen istemciler için görünmez olduğunu ve onların başlığı yalnızca o kaynağa ait sayacağını hatırlatır. Pratikte en anlaşılır yol, bir sürümün bütün yanıtlarına başlıkları eklemektir; aşağıdaki örnek de bunu bir ara katmanla yapıyor.

FastAPI ile çalışan örnek

Örnekte v1 uç noktaları 1 Aralık 2026'da kullanımdan kalkacak ve 1 Haziran 2027'de kapanacak. Ara katman yalnızca /v1/ ile başlayan yollara dokunuyor; kapanış tarihinden önce yanıta üç başlığı ekliyor, sonra 410 döndürüyor. Saati test edebilmek için bir sözlükte tutuyoruz; gerçek uygulamada datetime.now(timezone.utc) kullanılır:

python
from datetime import datetime, timezone
from email.utils import format_datetime

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient

KULLANIMDAN_KALKIS = datetime(2026, 12, 1, tzinfo=timezone.utc)
KAPANIS = datetime(2027, 6, 1, tzinfo=timezone.utc)
SIMDI = {"an": datetime(2026, 11, 7, tzinfo=timezone.utc)}  # test için ayarlanabilir saat

app = FastAPI()

@app.middleware("http")
async def v1_uyarisi(request: Request, call_next):
    if not request.url.path.startswith("/v1/"):
        return await call_next(request)
    if SIMDI["an"] >= KAPANIS:
        return JSONResponse({"detail": "v1 kapatıldı, /v2 kullanın"}, status_code=410)
    yanit = await call_next(request)
    yanit.headers["Deprecation"] = f"@{int(KULLANIMDAN_KALKIS.timestamp())}"
    yanit.headers["Sunset"] = format_datetime(KAPANIS, usegmt=True)
    yanit.headers["Link"] = ('<https://api.example.com/belgeler/v1-gecis>; rel="deprecation"; '
                             'type="text/html", <https://api.example.com/belgeler/surum-politikasi>; '
                             'rel="sunset"; type="text/html"')
    return yanit

@app.get("/v1/kullanicilar/{kid}")
def v1_getir(kid: int):
    return {"id": kid, "name": "Ayşe Demir"}

@app.get("/v2/kullanicilar/{kid}")
def v2_getir(kid: int):
    return {"id": kid, "full_name": "Ayşe Demir"}

c = TestClient(app)
r = c.get("/v1/kullanicilar/7")
print(r.status_code, r.json())
# çıktı: 200 {'id': 7, 'name': 'Ayşe Demir'}
for b in ("deprecation", "sunset", "link"):
    print(f"{b}: {r.headers[b]}")
# çıktı: deprecation: @1796083200
# çıktı: sunset: Tue, 01 Jun 2027 00:00:00 GMT
# çıktı: link: <https://api.example.com/belgeler/v1-gecis>; rel="deprecation"; type="text/html", <https://api.example.com/belgeler/surum-politikasi>; rel="sunset"; type="text/html"
print("v2 Deprecation var mı:", "deprecation" in c.get("/v2/kullanicilar/7").headers)
# çıktı: v2 Deprecation var mı: False

SIMDI["an"] = datetime(2027, 6, 2, tzinfo=timezone.utc)
r = c.get("/v1/kullanicilar/7")
print(r.status_code, r.json())
# çıktı: 410 {'detail': 'v1 kapatıldı, /v2 kullanın'}

Kodda iki biçim dönüşümüne dikkat edin. Deprecation değeri timestamp() ile saniyeye çevrilip başına @ ekleniyor. Sunset değeri ise Python standart kütüphanesindeki email.utils.format_datetime işleviyle usegmt=True verilerek IMF-fixdate biçiminde üretiliyor. Çıktıda v2 yanıtında başlık olmadığını, kapanıştan sonra ise v1 isteğinin 410 aldığını görüyorsunuz. Kullanımdan kaldırma döneminde v1'in yanıt gövdesi hiç değişmedi; RFC 9745'in "davranış değişmez" ilkesi tam olarak budur.

İstemci tarafında başlıkları okumak

Başlıkları dikkate alan bir istemci, iki tarihi ayrıştırıp kayda geçirebilir, kural ihlalini denetleyebilir ve kalan süreyi hesaplayıp ekibini uyarabilir. Standart kütüphane iki biçimi de okumaya yeter:

python
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime

basliklar = {"Deprecation": "@1796083200", "Sunset": "Tue, 01 Jun 2027 00:00:00 GMT"}

kalkis = datetime.fromtimestamp(int(basliklar["Deprecation"].lstrip("@")), tz=timezone.utc)
kapanis = parsedate_to_datetime(basliklar["Sunset"])
print("kullanımdan kalkış:", kalkis.isoformat())
# çıktı: kullanımdan kalkış: 2026-12-01T00:00:00+00:00
print("kapanış:", kapanis.isoformat())
# çıktı: kapanış: 2027-06-01T00:00:00+00:00
print("tutarlı mı:", kapanis >= kalkis)
# çıktı: tutarlı mı: True
print("kalan gün:", (kapanis - datetime(2026, 11, 7, tzinfo=timezone.utc)).days)
# çıktı: kalan gün: 206

parsedate_to_datetime, HTTP-date değerini saat dilimi bilgisi olan bir datetime nesnesine çevirir. Deprecation değeri ise @ işareti atılıp saniye olarak okunur. Gerçek bir istemcide bu kontrolü merkezi HTTP katmanına koyup başlığı gördüğünüzde bir uyarı kaydı üretmek, ekibin emeklilik takvimini kaçırmamasını sağlar.

OpenAPI belgesinde işaretlemek

Başlıklar çalışma anında bilgi verir; belgeler ise geliştiricinin kodu yazarken gördüğü yerdir. OpenAPI 3.1.1 belirtimi, işlem (operation) düzeyinde deprecated alanını tanımlar: true olduğunda kullanıcıların o işlemi kullanmaktan kaçınması beklenir. Yanıt başlıklarını da belgeye ekleyebilirsiniz. Aşağıdaki belge bir OpenAPI doğrulayıcısından geçirildi:

yaml
openapi: 3.1.1
info:
  title: Kullanıcı API
  version: "1.0"
paths:
  /v1/kullanicilar/{kid}:
    get:
      summary: Kullanıcıyı getir (v1)
      deprecated: true
      description: v1 1 Aralık 2026'da kullanımdan kalkar, 1 Haziran 2027'de kapanır. /v2 kullanın.
      parameters:
        - name: kid
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Kullanıcı bulundu
          headers:
            Deprecation:
              description: Kullanımdan kalkış tarihi (RFC 9745)
              schema:
                type: string
                examples:
                  - "@1796083200"
            Sunset:
              description: Kapanış tarihi (RFC 8594)
              schema:
                type: string
                examples:
                  - Tue, 01 Jun 2027 00:00:00 GMT
        "410":
          description: Sürüm kapatıldı

Belge yazımının ayrıntısı OpenAPI 3.1 YAML yazısında, FastAPI'nin otomatik belgesi OpenAPI ve Swagger yazısında anlatılıyor.

Kapanıştan sonra: 410 mu 404 mü?

RFC 9110, 410 Gone kodunu hedef kaynağa erişimin artık mümkün olmadığı ve bu durumun büyük olasılıkla kalıcı olduğu durumlar için tanımlar; sunucu durumun kalıcı olup olmadığını bilmiyorsa 404 Not Found kullanılmalıdır. RFC 8594'ün kendi örneği de kapanış sonrasında 410 dönülebileceğini, sunucu eski adresleri takip etmiyorsa daha genel bir 404 görülebileceğini söyler. Bilinçli olarak kapattığınız bir sürüm için 410 daha açıklayıcıdır ve gövdede yeni sürüme yönlendiren kısa bir mesaj vermek istemciye yol gösterir:

http
HTTP/1.1 410 Gone
Content-Type: application/json

{"detail": "v1 kapatıldı, /v2 kullanın"}

Hata gövdesini standart bir biçimde vermek isterseniz Problem Details (RFC 9457) yazısına bakın. Kapanıştan sonra hangi kodun döneceğini de emeklilik politikası belgesine yazmak, RFC 8594'ün politika bağlantısı için önerdiği içeriklerden biridir.

Örnek bir emeklilik takvimi

Örnek bir emeklilik takvimi: v2 yayımlanır, politika duyurulur, v1 kullanımdan kalkar, Geçiş dönemi, v1 kapanır

RFC'ler süreleri belirlemez; politika belgede sizin tarafınızdan tanımlanır. Aşağıdaki tablo bu yazıdaki örnek tarihlerle bir v1 emekliliğinin adımlarını gösterir:

AdımTarih (örnek)Yanıtlarda ne görünür
v2 yayımlanır, politika duyurulur7 Kasım 2026Link rel="sunset" politika bağlantısı
v1 kullanımdan kalkar1 Aralık 2026Deprecation, Sunset ve Link rel="deprecation"
Geçiş dönemiAralık 2026 - Mayıs 2027v1 aynı davranışla çalışır, başlıklar sürer
v1 kapanır1 Haziran 2027410 Gone ve yeni sürüme yönlendiren mesaj

Sunset önbellek bilgisi değildir

RFC 8594, Sunset başlığının HTTP önbelleklemesiyle karıştırılmaması gerektiğini ayrıca vurgular. Önbellekleme bir temsilin ne zaman yeniden kullanılabileceğiyle ilgilenir; Sunset ise kaynağın durumu hakkında hiçbir şey söylemez, yalnızca kaynağın ne zaman erişilemez olabileceğini bildirir. İkisi birbirini tamamlar. Önbellek başlıkları Cache-Control ve ETag yazısında anlatılıyor. RFC ayrıca bir güvenlik notu düşer: kapanış tarihi, örneğin bir kaydın son kullanma tarihini veya oluşturulma zamanını ele verebilir; bu bilgiyi yanıt gövdesinde gösterseydiniz nasıl koruyacaksanız başlıkta da öyle koruyun.

Sık yapılan hatalar

  • Sunset tarihini Deprecation tarihinden önceye koymak: RFC 9745 bunu açıkça yasaklar.
  • Tarih biçimlerini karıştırmak: Deprecation için @ ile Unix zamanı, Sunset için IMF-fixdate kullanılır.
  • İlk aşamada yalnızca Sunset göndermek: Sunset kapanış anı içindir; "artık önerilmiyor" bilgisi Deprecation ile verilir.
  • Kullanımdan kaldırırken davranışı değiştirmek: Deprecation döneminde kaynak eskisi gibi çalışmalıdır.
  • Yalnızca başlıklara güvenmek: Başlıkları okumayan istemciler duyuruyu görmez; belgede ve sürüm notlarında de duyurun.
  • Bağlantıyı HTTP üzerinden vermek: Geçiş belgesi bağlantılarını HTTPS ile sunun.
  • Kapanıştan sonra anlamsız hata: 500 veya boş yanıt yerine 410 ve yeni sürümü gösteren bir mesaj verin.

Sık Sorulan Sorular

Deprecation başlığı bir standart mı?

Evet. RFC 9745 Mart 2025'te standartlar yolunda (Standards Track) yayımlandı ve başlık IANA HTTP alan adları kayıt defterine kalıcı olarak eklendi.

Deprecation ile Sunset arasında ne kadar süre olmalı?

RFC'ler bir süre belirlemez. Süreyi istemcilerinizin geçiş kapasitesine göre seçin, emeklilik politikası belgesinde yazın ve rel="sunset" bağlantısıyla duyurun.

Sunset tarihi geçtiyse ama kaynak hâlâ çalışıyorsa ne olur?

Başlık bir ipucudur, garanti değildir. RFC 8594 geçmiş bir tarihi "her an kapanabilir" olarak yorumlamayı önerir; istemciler geçişi ertelememelidir.

Tek bir alanı kullanımdan kaldırmak için bu başlıklar uygun mu?

Başlıklar kaynak düzeyinde çalışır, tek bir JSON alanını işaretlemez. Alan düzeyindeki değişiklikleri belgede duyurmak ve önce genişlet, sonra daralt yaklaşımıyla yeni sürüme taşımak daha uygundur.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: REST API tasarımı öğrenme rehberi · API versiyonlama: URL ve header · Breaking change ve geriye uyumluluk · Problem Details ile hata yanıtı · HATEOAS ve Link başlığı