Sorgu sözleşmesi
Sayfalama, süzme, sıralama, genişletme, idempotency ve yeniden deneme — hepsi tek lehçe.
Keyset sayfalama
Tek lehçe vardır. cursor opak bir dizedir: kaynağın sıralama alanı ile id’sinden oluşan tuple’ı taşır, siz onu çözmez, yalnız geri verirsiniz. page_size varsayılan 25, tavan 100’dür ve tavanı aşan değer sessizce kırpılır. meta.next_cursor null ise o sayfa sonuncudur.
# İlk sayfa
GET /v1/contacts?page_size=100&sort=-created_at
# Yanıt
{
"data": [ … ],
"meta": { "next_cursor": "01JQ8ZC5N7W6Y2K…" }
}
# Sonraki sayfa — cursor AYNEN geri verilir, çözülmeye çalışılmaz
GET /v1/contacts?page_size=100&sort=-created_at&cursor=01JQ8ZC5N7W6Y2K…total alanı yoktur ve eklenmeyecektir: keyset ile doğru bir toplam ancak ikinci bir COUNT sorgusuyla verilebilirdi, o da sayfalamanın ucuz olma nedenini yok ederdi. Sayfa numarası (page=) da yoktur.
Süzme — filter[alan][operatör]
Operatör açıkça yazılır; operatörsüz filter[alan] biçimi tanınmaz — “eşitlik mi, içerir mi, aralık mı” sorusunu yanıtsız bırakan tam olarak o biçimdir.
| Operatör | Anlamı |
|---|---|
gte | büyük veya eşit |
lte | küçük veya eşit |
gt | büyük |
lt | küçük |
eq | eşit |
Hangi alanın hangi operatörleri kabul ettiği kaynak başına beyaz listedir ve sözleşmede x-filter altında yayımlanır. Zaman alanlarında değer RFC3339’dur. Tanınmayan alan, operatör ya da biçim sessizce yok sayılmaz: 400 validation_failed döner — süzülmemiş bir listeyi süzülmüş sanmanız imkânsızdır. Aynı süzgeci iki kez göndermek de 400’dür.
GET /v1/invoices?filter[created_at][gte]=2026-01-01T00:00:00Z&filter[created_at][lt]=2026-02-01T00:00:00Z⚠ Parantezler kodlanmadan gider ve imza ham sorgu dizesini alır — bkz. İmzalama.
Sıralama
sort=alan (artan) veya sort=-alan (azalan), tek alan. Beyaz liste kaynak başınadır (x-sort) ve dardır: cursor (sıra alanı, id) tuple’ı olduğu için keyset yalnız o anahtar üzerinde tutarlıdır. Verilmezse kaynağın varsayılanı uygulanır (listelerde -created_at).
Genişletme — include
include=ad[,ad2] ilişkiyi satır içi genişletir ve derinlik 1’dir; include=contact.address gibi noktalı bir ad 400 alır. JSON:API’nin included[] yan yükü alınmadı: yanıt invoice.contact = {…} şeklindedir, istemci tarafında birleştirme yoktur. Faturanın kalemleri gibi gömülü olan şey bir ilişki değildir, her zaman gövdededir.
Eşleşme yoksa alan hiç gelmez. include=contact istediğiniz bir faturada cari kartı bulunamazsa yanıtta contact anahtarı bulunmaz — null da gelmez. Bu bir hata değildir ve olağandır: cari kartı açılmamış bir alıcıya da fatura kesilir; belgenin kendi contact_title ve tax_number alanları her zaman durur. İstemciniz “anahtar var ama null” varsayımıyla yazılmışsa bu satırlarda patlar.
Belge uçlarında include=contact ayrıca cari kapsamını ister: kapsamı taşımayan anahtar 403 alır, genişletmesiz istek etkilenmez. Bu, her genişletmenin kuralı değildir — include=category stok kapsamında kalır, /v1/me ise hiçbir kapsam sormaz.
Idempotency
Idempotency-Key başlığı yalnız POST /v1/invoices/{id}/send ucunda okunur: aynı anahtarla ikinci istek saklanan yanıtı döndürür ve GİB’e ikinci bir gönderim olmaz. Aynı anahtarı başka bir istek için kullanmak 422 idempotency_key_reuse verir — her mantıksal işlem için yeni bir UUID üretin.
Kayıt oluşturan uçlarda (POST /v1/contacts, POST /v1/products, …) başlık okunmaz: yeniden gönderim ikinci bir kart yaratır. Gönderim ucu anahtarsız da çift göndermez, çünkü belge artık taslak olmadığından 409 alır.
Oran sınırı ve yeniden deneme
Tavan anahtar başına dakikada 60 istektir; jetonlu kova doludur, yani 60 isteklik bir yığın tek seferde geçer, sonrası saniyede bir istektir. İmzası doğrulanmış her yanıt X-RateLimit-Limit, X-RateLimit-Remaining ve X-RateLimit-Reset taşır; 429 ayrıca Retry-After verir. Kotayı istemci tarafında sabit bir sayıyla taklit etmeyin — başlıkların var olma sebebi budur.
Yeniden denenebilir olanlar: 408, 429 ve 5xx. Diğer 4xx yanıtları tekrar denemekle düzelmez.
deneme = 0
while deneme < 5:
yanit = istek_at()
if yanit.status == 429:
bekle(yanit.headers["Retry-After"]) # sunucunun verdiği saniye
elif yanit.status in (408, 500, 502, 503, 504):
bekle(min(2 ** deneme, 60) + rastgele(0, 1)) # üstel + jitter
else:
break # 4xx: tekrar deneme
deneme += 1