Sorgu sözleşmesi
Bütün uçların ortak kuralları: sayfalama, süzme, sıralama, genişletme, veri biçimleri, Idempotency-Key, oran sınırı ve yeniden deneme. Bir ucu ilk kez çağırmadan önce bir kez okuyun; ucun kendi alanları OpenAPI referansındadır.
Bu sayfadaki kurallar bütün /v1 uçlarında aynıdır. Hangi ucun hangi alanı süzdüğü, sıraladığı ya da genişlettiği aşağıdaki tabloda ve OpenAPI referansında (x-filter, x-sort, x-include) yazar. Bu kuralların bir fatura kesme akışında nasıl birleştiği için Fatura kesme akışı sayfasına bakın.
Sayfalama
Liste uçları imleç tabanlı (keyset) sayfalama kullanır. Sayfa numarası (page) ve toplam kayıt sayısı (total) yoktur: doğru bir toplam ikinci bir sayım sorgusu gerektirir, o da sayfalamayı pahalı yapar.
| Parametre | Kural |
|---|---|
page_size | Varsayılan 25, en çok 100. 100’den büyük değer hata vermez, 100’e indirilir. 1’den küçük ya da sayı olmayan değer 400 validation_failed alır. |
cursor | Önceki yanıttaki meta.next_cursor değeri. Opak bir dizedir: içeriğini çözmeyin, yalnız geri verin. Çözülemeyen değer 400 validation_failed alır. |
# İlk sayfa
GET /v1/units?page_size=100
# Yanıt
{
"data": [ … ],
"meta": { "next_cursor": "MjAyNi0wMy0xNVQxMTowMDowMC40ODI5MTNafDAxOTJlOGExLTdjM2YtN2QyZS1iNWE0LTNmNmM5ZDhlMWEyMA" }
}
# Sonraki sayfa: next_cursor değeri aynen geri verilir
GET /v1/units?page_size=100&cursor=MjAyNi0wMy0xNVQxMTowMDowMC40ODI5MTNafDAxOTJlOGExLTdjM2YtN2QyZS1iNWE0LTNmNmM5ZDhlMWEyMA
# Son sayfa
{ "data": [ … ], "meta": { "next_cursor": null } }meta.next_cursor null ise son sayfadasınız. Kayıtlar tam dolu bir sayfada bitse bile son sayfa null taşır; boş bir sonraki sayfayı istemeniz gerekmez. Tüm kayıtları okumak için döngü şöyle kurulur:
cursor = null
do:
yol = "/v1/units?page_size=100"
if cursor != null:
yol += "&cursor=" + cursor
yanit = istek_at("GET", yol)
isle(yanit.data)
cursor = yanit.meta.next_cursor
while cursor != nullSüzme
Süzgeç filter[alan][operatör]=değer biçiminde yazılır. Operatör açıkça belirtilir; operatörsüz filter[alan] tanınmaz.
| 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örü kabul ettiği uca göre değişir (aşağıdaki tablo). Değerin biçimi alanın türüne bağlıdır:
- Zaman alanları (
created_at): RFC 3339 zaman damgası, örneğin2026-01-01T00:00:00Z. Saniyenin altı kabul edilir. Yalnız tarih (2026-01-01) geçerli değildir, 400 alır. Saat farkındaki+karakteri%2Bolarak yazılır:2026-01-01T00:00:00%2B03:00. Yanıttaki bircreated_atdeğerini süzgeç olarak geri veriyorsanız saniye altı hassasiyetini kırpmayın: kırpılmış değer aynı saniyedeki kayıtları atlatır. - Kur tarihi (
date: kurlar): değer yine RFC 3339 olarak verilir ama yalnız gün kısmı kullanılır. - Belge tarihi (
issue_date: faturalar): yalnız gün,YYYY-MM-DDbiçiminde (örneğin2026-10-01). Saat taşıyan değer 400 alır.gtevelteo günü de kapsar. - Sözlük alanları (
status,document_type): yalnızeq, değer sözleşmedeki kümeden biri olmalıdır. - Metin alanları (
invoice_no): yalnızeq, tam eşleşme (parça arama yoktur), en çok 32 karakter. - Kimlik alanları (
uuidyani ETTN,envelope_idyani zarf kimliği): yalnızeq, UUID biçiminde.
Farklı süzgeçler birlikte uygulanır, yani kayıt hepsini sağlamalıdır. Aynı alana iki farklı operatör verebilirsiniz (aralık için gte ve lt). Aynı süzgeci iki kez göndermek 400 alır.
GET /v1/exchange-rates?filter[date][gte]=2026-01-01T00:00:00Z&filter[date][lt]=2026-02-01T00:00:00Z
GET /v1/invoices?filter[issue_date][gte]=2026-10-01&filter[issue_date][lte]=2026-10-31&filter[status][eq]=accepted
GET /v1/invoices?filter[invoice_no][eq]=YSF2026000000012Sözleşmede olmayan bir süzgeç, operatör ya da değer biçimi 400 alır; süzülmemiş bir listeyi süzülmüş sanmanız mümkün değildir.
Uca göre süzme, sıralama ve genişletme
Tüm alanlar gte, lte, gt ve lt operatörlerini kabul eder; istisnalar “Süzgeç alanları” sütununda belirtilmiştir. Varsayılan sıralama /v1/tax-rates dışında her yerde azalandır: -created_at, -date ya da -issue_date. /v1/tax-rates NACE koduna göre artan sıralanır, sırası sabittir ve sort kabul etmez.
| Uç | Süzgeç alanları | Sıralama | Genişletme |
|---|---|---|---|
/v1/invoices | issue_date (gte, lte); status, document_type, invoice_no, uuid, envelope_id (yalnız eq) | issue_date | contact |
/v1/inbox-invoices | issue_date (gte, lte); status, document_type, invoice_no, uuid (yalnız eq) | issue_date | yok |
/v1/exchange-rates | date | date | yok |
/v1/units | created_at | created_at | yok |
/v1/tax-rates | yok | nace_code (sabit, artan) | yok |
Fatura listelerinde (GET /v1/invoices, GET /v1/inbox-invoices) sıralama ve aralık süzgeci belge tarihi (issue_date) üzerindedir. Aralık süzgeci sıralama alanıyla aynıdır, çünkü farklı bir alana göre süzen liste sayfasını doldurmak için firmanın bütün kayıtlarını taramak zorunda kalırdı.
Tek kayıt okuyan uçlardan yalnız ikisi include kabul eder: /v1/invoices/{id} için contact, /v1/me için firma.
Sıralama
sort=alan artan, sort=-alan azalan sıralar. Tek alan verilebilir. Sıralanabilen alanlar uca göre değişir (yukarıdaki tablo) ve bilerek dardır: cursor sıralama alanı ile kayıt kimliğinden oluşan bir konum olduğu için sayfalama yalnız o alan üzerinde tutarlıdır. sort verilmezse tablodaki varsayılan uygulanır. Tabloda olmayan bir alan 400 alır.
Genişletme
include=ad ya da include=ad1,ad2 ilişkili kaydı yanıtın içine koyar. Örneğin GET /v1/invoices/{id}?include=contact faturanın içine contact alanını ekler; cari kartını ayrıca istemeniz gerekmez.
- Derinlik 1’dir:
include=contact.addressgibi noktalı bir ad 400 alır. Tabloda olmayan bir ad da 400 alır. - İlişki ayrı bir
includeddizisinde değil, kaydın içinde gelir:invoice.contact = {…}. İstemci tarafında birleştirme yapmazsınız. - Yazma uçları
includekabul etmez (bkz. “Yazma uçlarında doğrulama”).
Fatura detayında include=contact ayrıca cari kartı okuma kapsamını ister; anahtar bu kapsamı taşımıyorsa istek 403 forbidden alır, genişletmesiz istek etkilenmez. /v1/me?include=firma ise ek kapsam istemez.
Veri biçimleri
- Zaman damgası (
created_at,updated_at): RFC 3339, saniye altı hassasiyetle. - Tarih (
issue_date):YYYY-MM-DD. “Bugün” Europe/Istanbul saat dilimine göre hesaplanır (örneğin ileri tarihli fatura denetimi). - Tutar ve oran, okurken (
total,vat_rate,unit_price): ondalık metin, örneğin"1250.00". Para hesabında kayan noktalı (float) tür yerine ondalık (decimal) tür kullanın. - Boş alanlar: isteğe bağlı bir alan boşsa yanıtta hiç yer almaz,
nullgelmez. Her zaman gelen alanlar OpenAPI’dekirequiredlistesindedir. Tek istisnameta.next_cursor: son sayfadanullolur. - Tutar ve oran, yazarken: türü uca göre değişir. Fatura kaleminde
quantity,unit_price,vat_rateve stok kartındaunit_price,vat_rateJSON sayısıdır; stok hareketindekiquantity,unit_priceondalık metindir ("1250.50"). Her alanın türü OpenAPI referansında yazar.
Yazma uçlarında doğrulama
Yazma uçları (POST, PATCH) include, filter ve sort parametrelerinden birini alırsa 400 verir. Gövde en fazla 2 MiB olabilir; aşan istek 400 alır. Sözleşmede olmayan gövde alanı yok sayılır. Tanımlı bir alanın kuralına uymayan değer 400 validation_failed alır ve details.field o alanın istekteki adını taşır (tax_number, items[1].product_id gibi). Sık karşılaşılan kurallar:
- Sözlük alanları (cari
typeveperson_type, ürüntype, faturainvoice_typevescenario) yalnız sözleşmedeki değerleri alır. - Cari
tax_number10 haneli VKN,national_id11 haneli TCKN olmalı ve kontrol basamağı tutmalıdır. İstisnalar: nihai tüketici kimliği11111111111ve yurt dışındaki alıcının VKN’si2222222222. Kişininemailalanının biçimi denetlenir. - Ürün
unitdeğeri sözleşmedeki birim kodlarından biri ya da firmanın aktif bir birimi olmalıdır.currencybüyük harfli ISO 4217 kodudur (TRY,USD).vat_rate0, 1, 8, 10, 18 ya da 20 olabilir (fatura kaleminde de). Ürününunit_pricealanı negatif olamaz ve en fazla iki ondalık hane taşır.
Benzersiz olması gereken bir değer zaten kullanılıyorsa yanıt 409 conflict olur: aynı tax_number ya da code ile cari kartı, aynı code ile stok kartı, aynı adla stok kategorisi. Etiket adları benzersiz değildir.
Idempotency-Key
Idempotency, aynı isteği birden çok kez göndermenin tek kez göndermekle aynı sonucu vermesidir. API bunu yalnız POST /v1/invoices/{id}/send ucunda Idempotency-Key başlığıyla sağlar: aynı anahtarla gelen 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. Anahtar en çok 255 karakter olabilir; fazlası 400 validation_failed alır ve hiçbir şey yapılmaz. Anahtarlar API anahtarı başına ayrı tutulur: iki farklı API anahtarı aynı değeri seçse bile birbirinin yanıtını almaz. Gönderimi yeniden denemenin ayrıntısı Fatura kesme akışında.
Anahtar 24 saat saklanır. Süre dolduktan sonra aynı anahtarla gelen istek yeni bir istek sayılır ve yeniden çalışır. Saklanan kayıtlar saatlik bir işle silindiği için 24. saatin hemen ardından gelen bir istek hâlâ saklanan yanıtı alabilir; buna güvenmeyin. Çift gönderim koruması için, gönderimi yeniden denemeden önce gönderim sonucunu faturanın durumundan da (GET /v1/invoices/{id}) doğrulayın.
Anahtarsız gönderimde ikinci isteğin sonucu belgenin durumuna bağlıdır. Gönderilmiş, kabul edilmiş, reddedilmiş ya da iptal edilmiş belge 409 conflict alır; hata durumundaki (failed) ya da kuyrukta takılı kalmış belge ise yeniden kuyruğa alınır ve 202 döner. Ayrıntı OpenAPI referansında sendInvoice açıklamasındadır. Bu yüzden her gönderim niyeti için sabit bir Idempotency-Key kullanın. Belirsiz kalan bir gönderimde status alanını okuyarak durumu doğrulayın.
Oran sınırı
Oran sınırı jetonlu kovadır: kova dolu başlar, yani kapasite kadar isteklik bir yığın tek seferde geçer; kova boşaldıktan sonra hak dolum hızıyla yeniden doğar. İki sınıf vardır. Her sınıfta iki kova birlikte sayılır: anahtar başına ve firma toplamı (firmanın bütün anahtarları). Firma toplamı, aynı firma için birden çok anahtar açarak kotayı katlamayı önler.
| Sınıf | Anahtar başına | Firma toplamı |
|---|---|---|
Genel: her imzalı /v1 isteği | Kapasite 60, dakikada 120 | Kapasite 120, dakikada 300 |
| İndirme: yalnız fatura XML ve PDF uçları | Kapasite 10, dakikada 60 | Kapasite 20, dakikada 120; aynı anda en çok 4 indirme |
İndirme isteği hem genel hem indirme kovasından düşer. İndirme adımında reddedilen istek genel jetonu geri alır: Retry-After kadar bekleyip yeniden deneyen istemci kendi genel kotasını tüketmez. Liste uçları ve yazma uçları genel sınıftadır. Hangi ucun indirme sınıfında olduğu OpenAPI referansında x-oran-sinifi: download ile yazar; sayılar sözleşmenin x-oran-siniri bloğundadır.
İmzası doğrulanmış her yanıt şu başlıkları taşır:
| Başlık | Anlamı |
|---|---|
X-RateLimit-Resource | Aşağıdaki üç başlığın anlattığı kova: key:general, firm:general, key:download, firm:download; eşzamanlılık reddinde firm:download-concurrency. İsteğin sayıldığı kovalar içinde en dar olanı (kalan hakkı kapasitesine oranla en az olan) gösterilir. |
X-RateLimit-Limit | O kovanın kapasitesi, yani anlık yığılma tavanı (anahtarın genel kovası için 60). Eşzamanlılık reddinde firmanın aynı anda açabileceği indirme sayısı (4). |
X-RateLimit-Remaining | Kovada kalan istek hakkı; 429 yanıtında 0. |
X-RateLimit-Reset | Kovanın tamamen dolacağı an, Unix saniyesi olarak (süre farkı değil). Eşzamanlılık reddinde yeniden denenebilecek an. |
Retry-After | Yalnız 429 yanıtında gelir: bir sonraki istek hakkının doğmasına kalan saniye (en az 1). Eşzamanlılık reddinde 1. |
Bu başlıklar 401 yanıtında yoktur. Kotayı istemci tarafında sabit bir sayıyla taklit etmeyin; kalan hakkı başlıklardan okuyun. Aynı firmanın birden çok anahtarı varsa firma toplamı hepsinin ortak kotasıdır: bir anahtarın yoğun kullanımı diğerine firm:general ile 429 döndürebilir.
Yeniden deneme
- 429: istek işlenmemiştir.
Retry-Aftersaniyesi kadar bekleyip aynı isteği yeniden gönderin. - 500, 502, 503, 504 ve 408: üstel bekleme ve rastgele sapmayla (jitter) yeniden deneyin. API’nin kendisi yalnız 500 üretir ve gövdesi hata zarfıdır; 502, 503 ve 504 ağ katmanından gelir, gövdesi JSON olmayabilir. Gövdeyi çözmeden önce
Content-Typebaşlığına bakın. - Diğer 4xx: yeniden göndermek sonucu değiştirmez. İsteği düzeltin; 409 için önce kaydın güncel durumunu okuyun.
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 bekleme + jitter
else:
break # başarı ya da yeniden denenmeyecek 4xx
deneme += 1