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.

HTTP
# İ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örAnlamı
gtebüyük veya eşit
lteküçük veya eşit
gtbüyük
ltküçük
eqeş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.

HTTP
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.

Sözde kod
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