Bir REST API'yi yayına almadan önce şu sorulara "evet" diyebiliyor olmanız gerekir: URL'ler fiil değil çoğul isim mi, her işlem doğru HTTP metodunu ve doğru durum kodunu kullanıyor mu, hatalar bütün uç noktalarda aynı biçimde mi, koleksiyonlar sayfalı mı, filtre ve sıralama sorgu parametresiyle mi yapılıyor, bir sürümleme stratejisi var mı, kimlik doğrulama ve hız sınırı (rate limit) uygulanıyor mu, tarayıcıdan erişilecekse CORS doğru ayarlı mı, OpenAPI belgesi var mı, ödeme gibi kritik işlemler tekrar denemeye dayanıklı mı, istek gövdeleri doğrulanıyor mu ve yanıtlarda gizli bilgi sızıyor mu. Bu yazı bu maddeleri tek tek, her birinin gerekçesi ve ayrıntısını anlatan yazının bağlantısıyla veriyor.
Listenin çekirdeği, bu serinin dayandığı API tasarımı rehberinin "yayından önce" kontrol listesidir; maddelerin bir kısmı RFC metinlerine, bir kısmı yaygın tasarım tercihlerine dayanır ve aradaki farkı her maddede belirtiyoruz. Sonda iki çalıştırılmış örnek var: OpenAPI belgesini birkaç maddeye karşı otomatik tarayan küçük bir betik ve maddelerin davranışla doğrulanabilenlerini sınayan bir duman testi. Seriyi baştan izlemek isterseniz REST API tasarımı öğrenme rehberi yazısından başlayabilirsiniz.
Listeyi nasıl kullanmalı
Kontrol listesi bir tasarım belgesinin yerini tutmaz; tasarım kararlarını verdikten sonra "unuttuğumuz bir şey var mı" sorusunu sormanın yapılandırılmış hâlidir. En verimli kullanım, her maddeyi API'nizdeki somut bir kanıta bağlamaktır: "Sayfalama var" demek yerine "GET /v1/kullanicilar limit parametresi alıyor, üst sınırı 100, belgede yazıyor" diyebilmek. Kanıt gösteremediğiniz madde, yapılmış sayılmaz. Maddelerin bir kısmı belgeden (OpenAPI dosyası), bir kısmı da yalnızca çalışan sistemden doğrulanabilir; aşağıdaki örnekler ikisini ayrı ayrı ele alıyor.
1. URL ve kaynak tasarımı
- Kaynaklar isimdir, fiil değil.
/usersdoğru,/getUsersve/createUseryanlış. Eylemi HTTP metodu söyler; URL'ye fiil koymak aynı bilgiyi iki yere yazmak ve her yeni eylem için yeni bir adres icat etmek demektir. Ayrıntı: endpoint isimlendirme kuralları, REST API nedir. - Kaynak adları çoğuldur.
/usersve/users/123; tekil/userdeğil. Koleksiyon ile içindeki tek öğe aynı kökü paylaşır ve istemci adresleri tahmin edebilir. Bu bir standart değil, rehberin önerdiği tutarlılık tercihidir; önemli olan bütün API'de aynı kuralı izlemektir. - Hiyerarşi sığdır.
/users/123/postsiyi;/users/123/posts/456/comments/789/repliesfazla derin. Rehber en fazla üç seviye önerir ve derin bir zincir yerine/comments/789/repliesgibi kısa bir yol kullanmayı söyler. Ayrıntı: iç içe kaynaklar ve URL derinliği. - Kimlik yolda, filtre sorguda.
/users/123bir kaynağı gösterir;/users?role=adminbir koleksiyonu daraltır. Ayrıntı: path ve query parametresi farkı.
2. Metotlar ve durum kodları

- Her metot kendi işini yapar. GET okur, POST oluşturur, PUT tamamen değiştirir, PATCH kısmen günceller, DELETE siler. Veri değiştiren bir GET, önbelleklerin ve tarayıcıların "güvenli" saydığı bir isteğin yan etki yaratması demektir. Metotların tanımı: HTTP istek ve yanıt yapısı; CRUD eşlemesi: CRUD endpoint tasarımı; PUT ile PATCH: PUT ve PATCH farkı.
- Başarı kodu işleme uygundur. Okuma ve güncelleme için 200, oluşturma için 201 ile
Locationbaşlığı, gövdesiz silme için 204. - Hata kodu sorunu anlatır. 400 bozuk istek, 401 kimlik yok ya da geçersiz, 403 yetki yok, 404 kaynak yok, 409 çakışma, 422 doğrulama hatası, 429 çok fazla istek, 500 sunucu hatası, 503 geçici olarak hizmet dışı. Hata durumunda 200 ile
{"success": false}döndürmek, istemcilerin hata denetimini ve izleme araçlarını kör eder. Ayrıntı: durum kodu seçimi, 401 ve 403 farkı, genel liste: HTTP durum kodları. - 401 yanıtı WWW-Authenticate taşır. Bu bir tercih değil, RFC 9110'un zorunlu kuralıdır: 401 üreten sunucu en az bir kimlik doğrulama yöntemini bildiren bu başlığı göndermelidir (MUST).
3. İstek ve yanıt biçimi
- Hata biçimi tektir. Rehber iki seçenek sunar: RFC 7807 Problem Details ya da kendi biçiminiz. RFC 7807'nin yerini bugün RFC 9457 almıştır; rehber metni hâlâ 7807 adını kullanır. Hangisini seçerseniz seçin, bütün uç noktalar aynı yapıyı döndürmelidir; aksi hâlde istemci her uç nokta için ayrı hata ayrıştırıcısı yazar. Ayrıntı: Problem Details (RFC 9457), hata mesajı formatı.
- İstek gövdesi doğrulanır. Tür, zorunlu alan ve değer aralığı sunucuda denetlenir; geçersiz gövde veritabanına ulaşmadan 400 ya da 422 ile reddedilir. Ayrıntı: istek doğrulama, 400 ve 422.
- JSON yapısı ve alan adları tutarlıdır. Rehber anahtarlar için
snake_case, zaman damgaları içincreated_atgibi adlar önerir; tarih değerleri RFC 3339 biçimindedir. Koleksiyon yanıtlarıdatavemetazarfıyla döner. Ayrıntı: JSON yanıt yapısı, alan adlandırma ve tarih formatı. - Medya türleri açıktır. Yanıtlar doğru
Content-Typeile etiketlenir, desteklenmeyen istek gövdesi 415 alır. Ayrıntı: içerik anlaşması.
4. Koleksiyonlar

- Her koleksiyon sayfalıdır. Bugün on kayıt dönen bir liste bir yıl sonra yüz bin kayıt döndürebilir; sayfalamayı sonradan eklemek, yanıt yapısını değiştirdiği için kırıcı bir değişikliktir.
limitiçin bir varsayılan ve bir üst sınır belirleyin. Ayrıntı: offset sayfalama, cursor pagination. - Filtre ve sıralama sorgu parametresidir.
?role=admin&status=active,?sort=-created_at. Sıralama alanları bir izin listesine göre denetlenir; istemcinin gönderdiği alan adını doğrudan veritabanı sütununa çevirmek gizli alanların sıralamayla dolaylı olarak sızmasına ya da sunucu hatasına yol açabilir. Ayrıntı: filtreleme, sıralama ve arama. - Toplu işlemler sınırlıdır.
POST /users/batchgibi uç noktalar bir öğe sayısı sınırı ve öğe bazında sonuç bildirir. Ayrıntı: toplu işlem endpointi.
5. Sürüm ve değişiklik yönetimi
- Bir sürümleme stratejisi vardır. Rehber
/v1/usersbiçimindeki URL sürümlemeyi önerir: basittir, yönlendirmesi kolaydır, önbellekte ayrı tutulur. Başlık sürümlemesi de bir seçenektir. Önemli olan ilk günden bir sürüm öneki koymaktır; sürümsüz yayınlanmış bir API'ye sonradan sürüm eklemek zordur. Ayrıntı: API sürümleme. - Kırıcı değişiklikler bilinir. Alan silmek, yeniden adlandırmak, türünü değiştirmek ve yeni zorunlu alan eklemek yeni sürüm gerektirir; isteğe bağlı alan eklemek gerektirmez. Ayrıntı: breaking change ve geriye uyumluluk.
- Eski sürümün kapanışı duyurulur.
Deprecation(RFC 9745) veSunset(RFC 8594) başlıkları istemcilere takvimi makinece okunur biçimde verir. Ayrıntı: Deprecation ve Sunset başlıkları.
6. Güvenlik ve trafik
- Kimlik doğrulama uygulanır. Servisler arası kullanım için API anahtarı, kullanıcı oturumları için JWT, rehberde önerilen iki yöntemdir; ikisi de
Authorization: Bearerbaşlığıyla taşınır (RFC 6750). Ayrıntı: API key ile kimlik doğrulama, JWT Bearer token, saldırılar: JWT güvenliği, üçüncü taraf erişimi: OAuth 2.0 ve OIDC. - Hız sınırı vardır. Sınır aşıldığında 429 Too Many Requests döner; RFC 6585 bu yanıtın durumu açıklayan bir içerik taşıması gerektiğini ve
Retry-Afteriçerebileceğini söyler.X-RateLimit-*başlıkları yaygın bir gelenektir, RFC standardı değildir. Algoritmalar ve başlıklar: API rate limiting stratejileri. - CORS yalnızca gerekiyorsa ve dar ayarlıdır. Tarayıcıdan çağrılmayan bir API'nin CORS ayarına ihtiyacı yoktur. Gerekiyorsa izin verilen kökenler tek tek yazılır; rehber
allow_origins=["*"]ayarını yalnızca geliştirme için uygun görür. Ayrıntı: CORS nedir, CORS hataları. - Yanıtlarda gizli bilgi yoktur. Parola özetleri, API anahtarları, iç kimlikler ve hata ayıklama izleri istemciye gitmez; yanıt şeması açıkça tanımlanır. Ayrıntı: REST API güvenlik rehberi, REST API tasarım hataları.
7. Belgeleme ve dayanıklılık

- OpenAPI belgesi vardır ve doğrulanmıştır. Her işlemin başarılı yanıtı ve bilinen hataları belgelenir. Ayrıntı: OpenAPI ve Swagger, OpenAPI 3.1 YAML yazma.
- Kritik POST işlemleri tekrar denemeye dayanıklıdır. Ağ hatası sonrası tekrarlanan bir ödeme isteği ikinci bir ödeme yaratmamalıdır; rehber bunun için
Idempotency-Keybaşlığını önerir (bir RFC standardı değil, yaygın bir gelenek). Ayrıntı: Idempotency-Key kullanımı. - İsteğe bağlı desenler bilinçli seçilir. Olay bildirimi gerekiyorsa webhook, ilişkili kaynakları keşfettirmek istiyorsanız HATEOAS bağlantıları. Bunlar zorunlu değildir; eklendiklerinde de aynı tutarlılık kurallarına uymalıdırlar.
Belgeyi otomatik taramak
Listedeki maddelerin bir kısmı OpenAPI belgesine bakarak denetlenebilir. Aşağıdaki betik bir belgeyi okuyup altı basit kurala göre tarıyor: sunucu adresinde sürüm öneki var mı, üst düzey security tanımlı mı, yol parçaları fiille başlıyor mu, kaynak seviyesi üçü aşıyor mu, her işlemde en az bir 4xx yanıtı belgelenmiş mi ve koleksiyon GET işlemlerinde sayfalama parametresi var mı. Kurallar bilerek kaba tutuldu; amaç bir doğrulayıcı yazmak değil, unutulan maddeleri yakalamak.
import re
import sys
import yaml
FIILLER = ("get", "create", "update", "delete", "list", "add", "remove")
METOTLAR = ("get", "put", "post", "delete", "patch")
def denetle(belge: dict) -> list[str]:
sorunlar = []
sunucular = [s["url"] for s in belge.get("servers", [])]
if not any(re.search(r"/v\d+$", u) for u in sunucular):
sorunlar.append("sürüm: servers url içinde /v1 benzeri sürüm yok")
if "security" not in belge:
sorunlar.append("kimlik doğrulama: üst düzey security tanımlı değil")
for yol, oge in belge["paths"].items():
sabit = [p for p in yol.strip("/").split("/") if not p.startswith("{")]
for parca in sabit:
if parca.lower().startswith(FIILLER):
sorunlar.append(f"{yol}: fiille başlayan parça '{parca}'")
if len(sabit) > 3:
sorunlar.append(f"{yol}: {len(sabit)} kaynak seviyesi, en fazla 3 önerilir")
for metot in METOTLAR:
if metot not in oge:
continue
yanitlar = oge[metot]["responses"]
if not any(k.startswith("4") for k in yanitlar):
sorunlar.append(f"{metot.upper()} {yol}: hiç 4xx yanıtı belgelenmemiş")
if metot == "get" and not yol.endswith("}"):
adlar = {p.get("name") or p.get("$ref", "").rsplit("/", 1)[-1]
for p in oge[metot].get("parameters", [])}
if not adlar & {"limit", "Limit", "cursor"}:
sorunlar.append(f"GET {yol}: sayfalama parametresi yok")
return sorunlar
for dosya in sys.argv[1:]:
sonuc = denetle(yaml.safe_load(open(dosya, encoding="utf-8")))
print(dosya, len(sonuc), "sorun")
for s in sonuc:
print(" -", s)
# çıktı:
# tam.yaml 0 sorun
# taslak.yaml 8 sorun
# - sürüm: servers url içinde /v1 benzeri sürüm yok
# - kimlik doğrulama: üst düzey security tanımlı değil
# - /getUsers: fiille başlayan parça 'getUsers'
# - GET /getUsers: hiç 4xx yanıtı belgelenmemiş
# - GET /getUsers: sayfalama parametresi yok
# - /users/{id}/posts/{post_id}/comments/{comment_id}/replies: 4 kaynak seviyesi, en fazla 3 önerilir
# - GET /users/{id}/posts/{post_id}/comments/{comment_id}/replies: hiç 4xx yanıtı belgelenmemiş
# - GET /users/{id}/posts/{post_id}/comments/{comment_id}/replies: sayfalama parametresi yoktam.yaml, OpenAPI YAML yazma yazısında adım adım kurulan belgedir ve hiçbir kurala takılmadı. taslak.yaml ise aşağıdaki gibi, bilerek kötü tasarlanmış ama şartname açısından geçerli bir belge:
openapi: 3.1.1
info:
title: Taslak API
version: 0.1.0
servers:
- url: https://api.ornek.com.tr
paths:
/getUsers:
get:
responses:
'200':
description: Kullanıcılar
/users/{id}/posts/{post_id}/comments/{comment_id}/replies:
get:
parameters:
- {name: id, in: path, required: true, schema: {type: integer}}
- {name: post_id, in: path, required: true, schema: {type: integer}}
- {name: comment_id, in: path, required: true, schema: {type: integer}}
responses:
'200':
description: YanıtlarBu belge OpenAPI doğrulayıcısından hatasız geçiyor; buna rağmen tarama sekiz sorun buldu. Fark önemlidir: doğrulayıcı belgenin şartnameye uyduğunu söyler, tasarımın iyi olduğunu söylemez. Fiil kontrolü yalnızca İngilizce fiil öneklerine bakıyor; Türkçe adlar kullanıyorsanız listeye getir, olustur, sil gibi önekler eklemeniz gerekir. Benzer kuralları sürekli entegrasyon hattına koyarsanız her değişiklikte otomatik çalışırlar.
Davranışı sınayan bir duman testi
Bazı maddeler yalnızca çalışan uygulamada görülebilir: 401 yanıtında WWW-Authenticate var mı, oluşturma 201 ile Location döndürüyor mu, eksik alan 422 alıyor mu, limit üst sınırı gerçekten uygulanıyor mu, silme 204 mü dönüyor. Aşağıdaki küçük FastAPI uygulaması ve onu TestClient ile yoklayan satırlar bunu gösteriyor. Veriler bellekteki bir sözlükte; üretimde bir veritabanı kullanılır.
from fastapi import Depends, FastAPI, HTTPException, Query, Response
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
guvenlik = HTTPBearer(auto_error=False)
DB = {}
def kimlik(k: HTTPAuthorizationCredentials | None = Depends(guvenlik)):
if k is None or k.credentials != "test-anahtari":
raise HTTPException(status_code=401, detail="Kimlik doğrulanamadı",
headers={"WWW-Authenticate": "Bearer"})
class KullaniciOlustur(BaseModel):
email: str
ad: str
@app.get("/v1/kullanicilar", dependencies=[Depends(kimlik)])
def listele(page: int = Query(1, ge=1), limit: int = Query(20, ge=1, le=100)):
veri = list(DB.values())[(page - 1) * limit: page * limit]
return {"data": veri, "meta": {"total": len(DB), "page": page, "limit": limit}}
@app.post("/v1/kullanicilar", status_code=201, dependencies=[Depends(kimlik)])
def olustur(g: KullaniciOlustur, response: Response):
no = len(DB) + 1
DB[no] = {"id": no, **g.model_dump()}
response.headers["Location"] = f"/v1/kullanicilar/{no}"
return DB[no]
@app.delete("/v1/kullanicilar/{no}", status_code=204, dependencies=[Depends(kimlik)])
def sil(no: int):
if DB.pop(no, None) is None:
raise HTTPException(status_code=404, detail="Kullanıcı bulunamadı")
# Duman testi: kontrol listesinin davranışla doğrulanabilen maddeleri
c = TestClient(app)
A = {"Authorization": "Bearer test-anahtari"}
r = c.get("/v1/kullanicilar")
print("kimlik yok:", r.status_code, r.headers.get("www-authenticate"))
r = c.post("/v1/kullanicilar", json={"email": "ayse@ornek.com.tr", "ad": "Ayşe"}, headers=A)
print("oluştur:", r.status_code, r.headers.get("location"))
r = c.post("/v1/kullanicilar", json={"email": "eksik@ornek.com.tr"}, headers=A)
print("eksik alan:", r.status_code, r.json()["detail"][0]["loc"])
r = c.get("/v1/kullanicilar?limit=1000", headers=A)
print("üst sınır:", r.status_code)
r = c.get("/v1/kullanicilar", headers=A)
print("liste:", r.status_code, sorted(r.json()), r.json()["meta"])
r = c.delete("/v1/kullanicilar/1", headers=A)
print("sil:", r.status_code, repr(r.text))
r = c.delete("/v1/kullanicilar/1", headers=A)
print("tekrar sil:", r.status_code, r.json())
# çıktı:
# kimlik yok: 401 Bearer
# oluştur: 201 /v1/kullanicilar/1
# eksik alan: 422 ['body', 'ad']
# üst sınır: 422
# liste: 200 ['data', 'meta'] {'total': 1, 'page': 1, 'limit': 20}
# sil: 204 ''
# tekrar sil: 404 {'detail': 'Kullanıcı bulunamadı'}Her satır bir maddeye karşılık geliyor. Kimlik bilgisi gönderilmeyen istek 401 aldı ve yanıt RFC 9110'un istediği WWW-Authenticate: Bearer başlığını taşıyor. Oluşturma 201 ve yeni kaynağın adresiyle döndü. Eksik ad alanı 422 ile ve hangi alanın eksik olduğunu gösteren konumla reddedildi. limit=1000 isteği üst sınıra takıldı. Liste yanıtı data ve meta zarfında geldi. Silme gövdesiz 204 döndü. Aynı kaynağı ikinci kez silmek 404 verdi; durum kodu farklı olsa da sunucudaki sonuç değişmedi, kaynak yine yok. Bu, rehberin listesindeki "POST, PUT ve DELETE için tekrar denemeyi sınayın" maddesinin DELETE tarafıdır.
Özet tablo
| Madde | Kontrol sorusu | Dayanak |
|---|---|---|
| İsim, çoğul | URL'de fiil ya da tekil kaynak adı var mı? | Tasarım tercihi (rehber) |
| Sığ hiyerarşi | Üç seviyeden derin yol var mı? | Tasarım tercihi (rehber) |
| Metot ve durum kodu | Hata durumunda 2xx dönen işlem var mı? | RFC 9110 durum kodu anlamları |
| 401 başlığı | 401 yanıtında WWW-Authenticate var mı? | RFC 9110 (MUST) |
| Hata biçimi | Bütün uç noktalar aynı hata yapısını mı döndürüyor? | RFC 9457 ya da kendi biçiminiz |
| Sayfalama | Her koleksiyonda limit ve üst sınır var mı? | Tasarım tercihi (rehber) |
| Sürüm | URL ya da başlıkta sürüm bilgisi var mı? | Tasarım tercihi (rehber) |
| Kimlik doğrulama | Korunması gereken her işlem korunuyor mu? | RFC 6750 Bearer |
| Hız sınırı | Sınır aşımında 429 dönüyor mu? | RFC 6585 |
| CORS | Köken listesi dar mı, gerekli mi? | Tasarım tercihi |
| Belge | OpenAPI belgesi doğrulayıcıdan geçiyor mu? | OpenAPI 3.1.1 |
| Idempotency | Kritik POST tekrarlanınca çift kayıt oluşuyor mu? | Gelenek (Idempotency-Key) |
| Doğrulama | Geçersiz gövde veritabanına ulaşabiliyor mu? | Tasarım tercihi |
| Gizli bilgi | Yanıtta parola özeti, anahtar ya da iz var mı? | Güvenlik ilkesi |
Sık Sorulan Sorular
Bütün maddeleri ilk sürümde karşılamak zorunda mıyım?
Hayır, ama bazılarını sonradan eklemek pahalıdır. Sürüm öneki, sayfalama ve hata biçimi yanıt yapısını etkilediği için ilk günden yerinde olmalıdır. Webhook ya da HATEOAS gibi desenler ihtiyaç doğduğunda eklenebilir.
Hangi maddeler standart, hangileri tercih?
Durum kodlarının anlamları, 401 ile birlikte WWW-Authenticate zorunluluğu, 429'un tanımı ve Problem Details biçimi RFC'lerde tanımlıdır. Çoğul kaynak adları, en fazla üç seviye, snake_case ve URL sürümleme ise rehberin gerekçesiyle önerdiği yaygın tercihlerdir.
Kontrol listesini kod incelemesinde nasıl kullanabilirim?
Yeni bir uç nokta içeren her değişiklikte inceleyen kişi listeden ilgili maddeleri sorabilir: "Bu liste sayfalı mı, hata biçimi ortak şemayı mı kullanıyor, belgeye eklendi mi?" Otomatik tarama betiği bu soruların bir kısmını inceleme öncesinde cevaplar.
Sık yapılan hataların ayrı bir listesi var mı?
Var: REST API tasarım hataları yazısı aynı konuları ters yönden, yanlış ve doğru örnek çiftleriyle anlatıyor.
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 · REST API tasarım hataları · endpoint isimlendirme · durum kodu seçimi · sayfalama · sürümleme · API güvenliği