İçerik anlaşması (content negotiation), aynı kaynağın birden fazla temsili olduğunda istemciyle sunucunun hangi biçimde konuşacağına karar verdiği mekanizmadır. İstemci Accept başlığıyla yanıtın hangi medya türünde (media type) gelmesini istediğini söyler, gönderdiği gövdenin türünü ise Content-Type ile bildirir. Sunucu istenen türlerden birini üretebiliyorsa onu seçer ve yanıtın Content-Type değerine yazar; hiçbirini üretemiyorsa 406 Not Acceptable döndürebilir, istekle gelen gövdenin türünü desteklemiyorsa 415 Unsupported Media Type döndürür. Pratikte bir REST API için en sade karar şudur: JSON varsayılan biçimdir, başka biçimler yalnızca gerçekten ihtiyaç varsa eklenir.

Bu yazı başlıkların anlamını RFC 9110 metnine dayanarak açıklıyor, kalite değerlerinin (q) nasıl okunduğunu gösteriyor ve FastAPI ile JSON ya da XML döndüren, gerektiğinde 406 ve 415 üreten küçük bir uygulamayı çalıştırılmış çıktılarıyla veriyor. Başlıkla sürüm seçmek (vendor medya türü) API sürümleme yazısının, HTTP mesajının genel yapısı HTTP istek ve yanıt yapısı yazısının, önbellek ayrıntıları ise Cache-Control ve ETag yazısının konusu; burada yalnızca bağlantı veriliyor.

Kaynak bir şeydir, temsil başka bir şey

REST tasarımında URL bir kaynağı gösterir: /v1/kullanicilar/42 adresi "42 numaralı kullanıcı" kavramının kendisidir. O kullanıcının JSON olarak yazılmış hâli, XML olarak yazılmış hâli ya da CSV satırı bu kaynağın farklı temsilleridir (representation). İçerik anlaşması tam olarak bu ayrım sayesinde mümkün olur: adres değişmeden, istemcinin tercihine göre farklı bir temsil seçilir. Bu yüzden /v1/kullanicilar/42.json ve /v1/kullanicilar/42.xml gibi biçimi adrese gömen tasarımlar, aynı kaynağa iki ayrı kimlik vermiş olur; kimlik ile biçim birbirine karışır.

RFC 9110 bölüm 12, içerik anlaşmasının protokolde görünen üç kalıbını tanımlar. Aşağıdaki tablo bu üç kalıbı ve bir API tasarımında nerede karşınıza çıktığını özetliyor.

KalıpKim seçerAPI tasarımındaki karşılığı
Proaktif (proactive, sunucu güdümlü)Sunucu, isteğin Accept ve benzeri başlıklarına bakarakGET yanıtında JSON mu XML mi döneceğine karar vermek
Reaktif (reactive, istemci güdümlü)İstemci, sunucunun verdiği seçenek listesinden406 yanıtındaki desteklenen türler listesine bakıp yeniden istemek
İstek içeriği anlaşması (request content)İstemci, sunucunun önceki yanıtta bildirdiği tercihe göre415 yanıtındaki Accept başlığına bakıp gövdeyi doğru türde göndermek

RFC, proaktif anlaşmanın zayıf yanlarını da açıkça sayar: sunucu kullanıcı için neyin "en iyi" olduğunu kesin olarak bilemez, her istekte tercihleri göndermek verimsizdir ve gizlilik açısından risk taşır, sunucu kodu karmaşıklaşır ve paylaşılan önbelleklerde yanıtların yeniden kullanımı azalır. API dünyasında bu maddelerin en çok hissedileni üçüncüsüdür: desteklediğiniz her ek biçim, her uç noktada (endpoint) test edilmesi gereken yeni bir kod yolu demektir.

Accept, Content-Type ve Vary: hangi başlık ne iş görür

Accept, Content-Type ve Vary: hangi başlık ne iş görür: Accept, Accept, Content-Type, Vary

Üç başlık sık karıştırılır, çünkü üçü de medya türleriyle ilgilidir. Aradaki fark yöndedir: Accept "bana bunu gönder" der, Content-Type "elindeki şu" der, Vary ise önbelleklere "bu yanıtı seçerken şu başlıklara baktım" der.

BaşlıkNeredeAnlamı
AcceptİstekYanıt gövdesi için kabul edilen medya türleri ve tercih sırası
AcceptYanıtAynı kaynağa sonraki isteklerin gövdesinde tercih edilen türler
Content-Typeİstek ve yanıtO mesajdaki gövdenin medya türü
VaryYanıtTemsil seçiminde rol oynayan istek başlıklarının adları
406 Not AcceptableYanıt durumuAccept ile uyumlu bir temsil yok ve sunucu varsayılan biçim göndermek istemiyor
415 Unsupported Media TypeYanıt durumuİstek gövdesinin biçimi bu metot ve kaynak için desteklenmiyor

Medya türü tür/alt-tür biçimindedir ve ardından noktalı virgülle ayrılmış parametreler gelebilir: application/json, text/html; charset=utf-8 gibi. RFC 9110 tür ve alt-tür adlarının büyük-küçük harf duyarsız olduğunu söyler; yani Application/JSON da aynı türdür. Parametre değerlerinin duyarlılığı ise parametreye göre değişir. Sunucu tarafında karşılaştırma yaparken değeri küçük harfe çevirip parametreleri ayırmanız bu yüzden gerekir; doğrudan dize eşitliği application/json; charset=utf-8 gibi geçerli bir değeri reddeder.

Aşağıda tipik bir alışveriş görülüyor. İstemci JSON istiyor, sunucu JSON üretiyor ve seçimin Accept başlığına göre yapıldığını Vary ile bildiriyor.

http
GET /v1/kullanicilar/42 HTTP/1.1
Host: api.ornek.com.tr
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json
Vary: Accept

{"id": 42, "ad": "Ayşe Yılmaz", "rol": "admin"}

İsteğin gövdesi olmadığı için ilk mesajda boş satırdan sonra bir şey yok; yanıtın gövdesi ise geçerli bir JSON nesnesi. Örnek alan adları Türkçe; bu serinin dayandığı API tasarımı rehberindeki /users gibi İngilizce adlar da eşit derecede yaygındır, önemli olan bir API içinde tek bir dile bağlı kalmaktır (bkz. endpoint isimlendirme kuralları).

Kalite değerleri: q parametresi nasıl okunur

Accept başlığı tek bir tür değil, virgülle ayrılmış bir liste taşıyabilir. Her öğeye q adlı bir ağırlık eklenebilir. RFC 9110 bölüm 12.4.2'ye göre bu ağırlık 0 ile 1 arasında bir sayıdır, en fazla üç ondalık basamak taşır, hiç yazılmadıysa 1 kabul edilir ve 0 değeri "kabul edilemez" anlamına gelir. Yani application/xml;q=0 "XML gönderme" demektir, "XML'i en az tercih ederim" değil.

http
GET /v1/kullanicilar/42 HTTP/1.1
Host: api.ornek.com.tr
Accept: application/xml;q=0.9, application/json

HTTP/1.1 200 OK
Content-Type: application/json
Vary: Accept

{"id": 42, "ad": "Ayşe Yılmaz", "rol": "admin"}

Bu istekte JSON'un ağırlığı yazılmadığı için 1'dir, XML'inki 0.9; sunucu iki türü de üretebildiğine göre doğru seçim JSON'dur. Listedeki sıra tek başına öncelik belirtmez, belirleyici olan ağırlıktır. Ağırlıklar eşitse RFC daha özel olan aralığın önce geldiğini söyler: text/plain;format=flowed, text/plain'den; o da text/*'dan; o da */*'dan önce gelir. Bir medya türünün ağırlığı, onunla eşleşen en özel aralığın ağırlığıdır.

Joker değerler de anlaşmanın parçasıdır: */* her türü, application/* o ana türün tüm alt türlerini kapsar. RFC'nin dikkat çektiği küçük bir ayrıntı işe yarar: istemci */*;q=0 ekleyerek "listedekilerden biri yoksa bana başka bir şey değil, 406 gönder" diyebilir. Yine de RFC aynı notta sunucunun bu tercihi görmezden gelmeye hakkı olduğunu, istemcinin farklı bir yanıtı da işleyebilmesi gerektiğini hatırlatır.

Sunucu ne zaman 406 döndürmeli

Sunucu ne zaman 406 döndürmeli

RFC 9110 bölüm 12.4.1, uygun temsil bulunamadığında sunucuya iki seçenek tanır: başlığa uyup 406 göndermek ya da başlığı yok sayıp yanıtı içerik anlaşmasına tabi değilmiş gibi göndermek. Hangisinin daha iyi olduğu tasarım kararıdır. Bir API için makul bir yaklaşım şöyle özetlenebilir: Accept hiç yoksa ya da */* içeriyorsa varsayılan biçimle (JSON) yanıt verin; istemci yalnızca sizin üretemediğiniz türleri açıkça istiyorsa 406 ile neyi destekleyebildiğinizi söyleyin. Böylece başlık göndermeyen basit istemciler sorunsuz çalışır, yanlış türü bekleyen istemci ise hatayı erken fark eder.

RFC, 406 yanıtının mevcut temsillerin özelliklerini ve adreslerini listeleyen bir içerik taşıması gerektiğini (SHOULD) söyler. Bu listeyi Problem Details (RFC 9457) biçimindeki bir hata gövdesine uzantı alanı olarak koymak, hata biçiminizi bütün uç noktalarda aynı tutmanın kolay bir yoludur:

http
GET /v1/kullanicilar/42 HTTP/1.1
Host: api.ornek.com.tr
Accept: text/csv

HTTP/1.1 406 Not Acceptable
Content-Type: application/problem+json
Vary: Accept

{"type": "https://api.ornek.com.tr/hatalar/desteklenmeyen-tur", "title": "Desteklenmeyen yanıt türü", "status": 406, "detail": "Bu kaynak text/csv olarak sunulmuyor", "available": ["application/json", "application/xml"]}

available alanı RFC 9457'nin tanımladığı standart alanlardan biri değildir; RFC 9457 problem türlerinin kendi uzantı alanlarını tanımlamasına izin verir ve bu örnek tam olarak bunu yapıyor. Hata gövdesinin kendisinin de içerik anlaşmasına tabi olduğunu unutmayın: RFC 9110, Accept tercihlerinin hata ve durum bilgisi taşıyan temsillere de uygulandığını belirtir. İstemci yalnızca CSV kabul ettiğini söylediği hâlde size JSON hata gövdesi gelmesi bu nedenle "başlığı yok sayma" seçeneğinin bir örneğidir ve bu yaygın, kabul edilebilir bir davranıştır.

415: istemcinin gönderdiği gövde desteklenmiyorsa

406 yanıt yönüyle ilgilidir; 415 ise istek yönüyle. RFC 9110 bölüm 15.5.16'ya göre 415, gövdenin bu metot ve hedef kaynak için desteklenmeyen bir biçimde olduğunu anlatır; sorun Content-Type değerinden, Content-Encoding değerinden ya da verinin doğrudan incelenmesinden çıkabilir. Aynı bölüm, sebep desteklenmeyen medya türüyse yanıtta Accept başlığıyla hangi türlerin kabul edileceğinin bildirilebileceğini söyler. Bu, yukarıdaki tablodaki "istek içeriği anlaşması" kalıbının ta kendisidir.

http
POST /v1/kullanicilar HTTP/1.1
Host: api.ornek.com.tr
Content-Type: text/plain

{"ad": "Mehmet Demir", "rol": "editor"}

HTTP/1.1 415 Unsupported Media Type
Accept: application/json
Content-Type: application/problem+json

{"type": "https://api.ornek.com.tr/hatalar/desteklenmeyen-govde", "title": "Desteklenmeyen gövde türü", "status": 415, "detail": "Yalnızca application/json kabul edilir"}

Gövde içerik olarak JSON görünse bile istemci onu text/plain diye etiketlemiş. Sunucunun bu etiketi düzeltip veriyi JSON gibi okuması cazip gelebilir, ancak RFC 9110 bölüm 8.3 içeriğe bakarak tür tahmin etmenin (MIME sniffing) yanlış sonuçlara ve güvenlik risklerine yol açabildiğini anlatır. Açık bir 415, istemci tarafındaki hatayı görünür kılar. Gövdenin türü doğru ama içeriği bozuksa ya da alanlar kurallara uymuyorsa 415 değil 400 veya 422 uygundur; bu ayrımı 400 ve 422 farkı yazısı ayrıntılı anlatıyor.

FastAPI ile örnek: JSON, XML ve 406

Başvuru rehberindeki örnek uygulama, Accept değerinin içinde application/xml dizesi geçiyor mu diye bakar ve geçiyorsa XML döndürür. Başlangıç için anlaşılır bir fikir, ancak q değerlerini tamamen görmezden gelir. Aşağıdaki küçük deneme, FastAPI'nin JSON gövdeyi farklı Content-Type değerleriyle nasıl karşıladığını ve dize aramasının neden yanıltıcı olduğunu gösteriyor:

python
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel

app = FastAPI()

class KullaniciOlustur(BaseModel):
    ad: str
    rol: str

@app.post("/v1/kullanicilar", status_code=201)
def kullanici_olustur(govde: KullaniciOlustur):
    return {"id": 43, **govde.model_dump()}

c = TestClient(app)
govde = '{"ad": "Mehmet Demir", "rol": "editor"}'
for tur in ["application/json", "text/plain", None]:
    h = {"Content-Type": tur} if tur else {}
    r = c.post("/v1/kullanicilar", content=govde.encode("utf-8"), headers=h)
    print(repr(tur), r.status_code, r.text)

# beceri dosyasındaki saf kontrol: alt dize araması
for accept in ["application/json, application/xml;q=0.5", "application/xml;q=0"]:
    print(repr(accept), "application/xml" in accept)

# çıktı:
# 'application/json' 201 {"id":43,"ad":"Mehmet Demir","rol":"editor"}
# 'text/plain' 422 {"detail":[{"type":"model_attributes_type","loc":["body"],"msg":"Input should be a valid dictionary or object to extract fields from","input":"{\"ad\": \"Mehmet Demir\", \"rol\": \"editor\"}"}]}
# None 201 {"id":43,"ad":"Mehmet Demir","rol":"editor"}
# 'application/json, application/xml;q=0.5' True
# 'application/xml;q=0' True

Çıktının ilk üç satırı FastAPI 0.128'in davranışını gösteriyor: text/plain etiketli gövde Pydantic modeline dönüştürülmüyor ve otomatik 422 yanıtı geliyor; hiç Content-Type gönderilmediğinde ise çatı gövdeyi JSON olarak okuyup isteği kabul ediyor. Son iki satır dize aramasının iki hatasını ortaya koyuyor: JSON'u açıkça daha çok tercih eden istemciye XML seçilir, XML'i q=0 ile reddeden istemciye bile XML seçilir.

Aşağıdaki sürüm başlığı öğelerine ayırıyor, ağırlığa göre sıralıyor, q=0 öğelerini atlıyor, joker değerleri varsayılan JSON'a bağlıyor ve hiçbir eşleşme yoksa 406 döndürüyor. Her yanıtta Vary: Accept da var. Veritabanı yerine bellekteki bir sözlük kullanılıyor; üretimde bu veri paylaşılan bir depodan gelir.

python
from xml.etree.ElementTree import Element, SubElement, tostring
from fastapi import FastAPI, Request, Response
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient

app = FastAPI()
KULLANICILAR = {42: {"id": 42, "ad": "Ayşe Yılmaz", "rol": "admin"}}
DESTEKLENEN = ["application/json", "application/xml"]

def tercihler(accept: str):
    """Accept değerini (tür, q) çiftlerine ayırır, q'ya göre sıralar."""
    sonuc = []
    for parca in accept.split(","):
        parca = parca.strip()
        if not parca:
            continue
        tur, *parametreler = [x.strip() for x in parca.split(";")]
        q = 1.0
        for prm in parametreler:
            if prm.lower().startswith("q="):
                q = float(prm[2:])
        sonuc.append((tur.lower(), q))
    return sorted(sonuc, key=lambda x: x[1], reverse=True)

def sec(accept: str | None):
    if not accept:
        return DESTEKLENEN[0]          # tercih yok: varsayılan JSON
    for tur, q in tercihler(accept):
        if q == 0:
            continue
        if tur == "*/*" or tur == "application/*":
            return DESTEKLENEN[0]
        if tur in DESTEKLENEN:
            return tur
    return None

def xml_yap(k: dict) -> bytes:
    kok = Element("kullanici")
    for ad, deger in k.items():
        SubElement(kok, ad).text = str(deger)
    return tostring(kok, encoding="utf-8")

@app.get("/v1/kullanicilar/{kullanici_id}")
def kullanici_getir(kullanici_id: int, request: Request):
    k = KULLANICILAR[kullanici_id]
    tur = sec(request.headers.get("accept"))
    basliklar = {"Vary": "Accept"}
    if tur is None:
        return JSONResponse(
            status_code=406,
            content={"detail": "Desteklenen türler", "available": DESTEKLENEN},
            headers=basliklar,
        )
    if tur == "application/xml":
        return Response(xml_yap(k), media_type="application/xml", headers=basliklar)
    return JSONResponse(k, headers=basliklar)

c = TestClient(app)
for accept in [None, "application/json", "application/xml",
               "application/xml;q=0.9, application/json", "text/csv", "text/csv, */*;q=0.1"]:
    h = {"Accept": accept} if accept else {}
    r = c.get("/v1/kullanicilar/42", headers=h)
    print(repr(accept), r.status_code, r.headers["content-type"], r.headers.get("vary"))
    print(r.text)

# çıktı:
# None 200 application/json Accept
# {"id":42,"ad":"Ayşe Yılmaz","rol":"admin"}
# 'application/json' 200 application/json Accept
# {"id":42,"ad":"Ayşe Yılmaz","rol":"admin"}
# 'application/xml' 200 application/xml Accept
# <kullanici><id>42</id><ad>Ayşe Yılmaz</ad><rol>admin</rol></kullanici>
# 'application/xml;q=0.9, application/json' 200 application/json Accept
# {"id":42,"ad":"Ayşe Yılmaz","rol":"admin"}
# 'text/csv' 406 application/json Accept
# {"detail":"Desteklenen türler","available":["application/json","application/xml"]}
# 'text/csv, */*;q=0.1' 200 application/json Accept
# {"id":42,"ad":"Ayşe Yılmaz","rol":"admin"}

İlk satırdaki None, örnekte başlığı bizim eklemediğimiz anlamına geliyor; TestClient'ın kullandığı httpx yine de varsayılan olarak Accept: */* gönderdiği için yanıt JSON oldu. Gerçek istemcilerde de durum çoğu zaman böyledir: başlık hiç olmayabilir ya da joker değer taşıyabilir. Beşinci satır 406 durumunu, son satır ise */*;q=0.1 öğesinin CSV bulunamadığında JSON'a izin verdiğini gösteriyor. Ayrıştırıcı bilerek sade tutuldu; parametreli medya aralıklarının özgüllük kurallarını ve hatalı q değerlerini ayrıntılı ele almıyor. Üretimde bu işi denenmiş bir kütüphaneye ya da çatınızın kendi desteğine bırakmanız daha güvenli olur.

İstek yönü için ayrı bir denetim gerekir. Aşağıdaki ara katman (middleware), gövde taşıyan metotlarda Content-Type değerinin parametreleri ayıklanmış hâline bakıyor ve JSON değilse 415 ile birlikte Accept: application/json döndürüyor:

python
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient
from pydantic import BaseModel

app = FastAPI()

class KullaniciOlustur(BaseModel):
    ad: str
    rol: str

@app.post("/v1/kullanicilar", status_code=201)
def kullanici_olustur(govde: KullaniciOlustur):
    return {"id": 43, **govde.model_dump()}

@app.middleware("http")
async def tur_denetimi(request: Request, call_next):
    if request.method in ("POST", "PUT", "PATCH"):
        tur = request.headers.get("content-type", "").split(";")[0].strip().lower()
        if tur != "application/json":
            return JSONResponse(
                status_code=415,
                content={"detail": "Yalnızca application/json kabul edilir"},
                headers={"Accept": "application/json"},
            )
    return await call_next(request)

c = TestClient(app)
govde = '{"ad": "Mehmet Demir", "rol": "editor"}'
for tur in ["application/json", "application/json; charset=utf-8", "text/plain", None]:
    h = {"Content-Type": tur} if tur else {}
    r = c.post("/v1/kullanicilar", content=govde.encode("utf-8"), headers=h)
    print(repr(tur), r.status_code, r.headers.get("accept"), r.text)

# çıktı:
# 'application/json' 201 None {"id":43,"ad":"Mehmet Demir","rol":"editor"}
# 'application/json; charset=utf-8' 201 None {"id":43,"ad":"Mehmet Demir","rol":"editor"}
# 'text/plain' 415 application/json {"detail":"Yalnızca application/json kabul edilir"}
# None 415 application/json {"detail":"Yalnızca application/json kabul edilir"}

application/json; charset=utf-8 değeri parametre ayrıldıktan sonra kabul ediliyor. Başlığı hiç göndermeyen istemci bu sürümde 415 alıyor; bu, yukarıda gördüğümüz FastAPI varsayılanından daha katı bir tercih. Hangisini seçeceğinize API'nizin kullanıcılarına göre karar verin, ama kararı belgeleyin.

Vary başlığı ve önbellek

Yanıtın içeriği Accept başlığına göre değişiyorsa bunu önbelleklere bildirmeniz gerekir. RFC 9110 bölüm 12.5.5'e göre Vary iki iş görür: önbelleğe, listelenen başlıkları aynı olmayan bir sonraki isteğe bu yanıtı vermemesini söyler; istemciye de yanıtın içerik anlaşmasına tabi olduğunu ve başka değerlerle farklı bir temsil gelebileceğini bildirir. RFC, seçici olarak yeniden kullanılmasını istediğiniz önbelleklenebilir yanıtlarda sunucunun Vary üretmesi gerektiğini (SHOULD) söyler. Vary: Accept unutulursa, önce XML isteyen bir istemcinin yanıtı önbellekte kalıp sonradan JSON isteyen istemciye verilebilir. Önbellek davranışının geri kalanı HTTP önbellek ve ETag rehberinde anlatılıyor.

Vendor medya türleri: kısa bir not

Bazı API'ler sürüm bilgisini de içerik anlaşmasıyla taşır: Accept: application/vnd.myapi.v1+json. Bu yaklaşım aynı URL'yi korur, fakat başvuru rehberinin de belirttiği gibi test etmesi daha zordur ve önbellekte ayrı tutulması için doğru Vary ayarı ister. Hangi durumda URL sürümlemenin, hangi durumda başlık sürümlemenin seçildiği URL ve header ile API sürümleme yazısında karşılaştırılıyor.

Sık yapılan hatalar

Sık yapılan hatalar: Dize araması, Yanlış etiket, Vary unutmak, Joker değere 406
  • Dize araması: "xml" in accept gibi bir kontrol q değerlerini yok sayar ve q=0 ile reddedilen türü seçebilir.
  • Yanlış etiket: XML üretip yanıtı Content-Type: application/json ile göndermek istemcinin ayrıştırıcısını bozar. Gövdeyi kim üretiyorsa etiketi de o koymalıdır.
  • Vary unutmak: Biçime göre değişen yanıtlar önbellekte birbirine karışır.
  • Joker değere 406: */* gönderen istemciye 406 döndürmek anlaşmayı tersine işletmektir; bu istemci her türü kabul ettiğini zaten söylemiştir.
  • İstek türünü denetlememek: Content-Type değerine bakmadan gövdeyi tahminle ayrıştırmak, RFC'nin uyardığı tür tahmini risklerini API'ye taşır.
  • Parametreyi hesaba katmamak: application/json; charset=utf-8 değerini tam dize eşitliğiyle reddetmek gereksiz 415 hatası üretir.
  • Her biçimi her uç noktada sunmak: Kimse kullanmıyorsa XML desteği yalnızca test yükü getirir. JSON ile başlayıp talep geldikçe biçim eklemek daha sürdürülebilirdir.

Sık Sorulan Sorular

Accept başlığı yoksa sunucu ne yapmalı?

RFC 9110'a göre içerik anlaşması başlığının bulunmaması, istemcinin o boyutta bir tercihi olmadığı anlamına gelir. Sunucu varsayılan temsilini, API'ler için genellikle JSON'u gönderebilir.

406 yerine varsayılan JSON göndermek standarda aykırı mı?

Hayır. RFC, uygun temsil yoksa sunucunun 406 göndermeyi ya da başlığı yok sayıp yanıtı anlaşmaya tabi değilmiş gibi vermeyi seçebileceğini açıkça yazar. Önemli olan tutarlı olmak ve davranışı belgelemektir.

Content-Type ile Accept aynı istekte farklı olabilir mi?

Evet. İstemci gövdeyi JSON olarak gönderip (Content-Type: application/json) yanıtı XML olarak isteyebilir (Accept: application/xml). İki başlık farklı yönleri anlatır ve birbirinden bağımsız değerlendirilir.

Hata yanıtları da Accept'e uymak zorunda mı?

RFC 9110, tercihlerin hata ve durum bilgisi taşıyan içeriğe de uygulandığını söyler; yine de sunucu tercihi yok sayabilir. Pratikte birçok API hata gövdesini her zaman application/problem+json olarak gönderir; bu durumda bunu belgelerinizde açıkça yazın.

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 sürümleme · Problem Details · 400 ve 422 farkı · ETag ve önbellek · HTTP mesaj yapısı