API Kılavuzu
Programınızı HesapTUT'a bağlayın: tahsilattan tek istekte fatura kesin, cari ve ürün eşleyin, faturanın GİB durumunu ve PDF'ini alın. Makinelerin okuyacağı tanım: /api/v1/openapi.json (Postman / Swagger'a içe aktarılabilir).
1. Başlarken
Anahtarı HesapTUT verir. Önce bir test anahtarı (htk_test_…) alırsınız: istekleriniz size ayrılan deneme firmasına yazılır, belgeler GİB'e gitmez. Denemeler bitince canlı anahtar (htk_canli_…) verilir. Anahtar size tek kullanımlık bir bağlantıyla ulaşır; bir kez gösterilir, güvenli bir yerde (sunucu ortam değişkeni, parola yöneticisi) saklayın.
Firma izni: Canlı anahtar tek başına hiçbir firmaya erişemez. Hizmet verdiğiniz firma, size verilen bağlantı kodunu HesapTUT'ta Ayarlar → Bağlı Uygulamalar ekranına girip izin verir. İzni istediği an kaldırabilir.
Her isteğe şu başlığı ekleyin: Authorization: Bearer <anahtar>. Birden çok firmaya erişiminiz varsa hangi firma için çalıştığınızı X-Company-Id başlığıyla belirtin.
Hız sınırı: 10 saniyede 10 istek. Çok sayıda fatura için toplu ucu (/v1/invoices/quick/batch, istek başına 100 fatura) kullanın.
2. İlk istek — bağlantı kontrolü
curl https://app.hesaptut.com/api/v1/status -H "Authorization: Bearer htk_test_XXXXXXXX"Cevap anahtarınızın kime ait olduğunu, test mi canlı mı olduğunu ve hangi firmalara izniniz olduğunu listeler.
3. Tahsilattan tek istekte fatura
Programınızda bir tahsilat olduğunda tek bir istek yeter: cari bulunur ya da açılır, fatura oluşturulur, kesilir, GİB'e gönderilir (arka planda) ve tahsilat faturaya bağlanır. Herhangi bir adım hata verirse hiçbir şey yazılmaz.
curl -X POST https://app.hesaptut.com/api/v1/invoices/quick \
-H "Authorization: Bearer htk_test_XXXXXXXX" \
-H "Content-Type: application/json" \
-d '{
"external_id": "TAH-2026-000123",
"prices_include_vat": true,
"expected_total": 120.00,
"contact": {
"external_id": "MUS-17",
"type": "person",
"name": "Ahmet Yılmaz",
"tax_number": "12345678901",
"email": "ahmet@ornek.com",
"city": "İstanbul",
"district": "Kadıköy"
},
"lines": [
{ "name": "Kurye hizmet bedeli", "quantity": 1, "unit": "C62",
"unit_price": 120.00, "vat_rate": 20 }
],
"payment": { "amount": 120.00, "date": "2026-10-01" }
}'HTTP/1.1 201 Created
{
"data": {
"invoice": {
"id": "7f0c…-…", "number": "HTT2026000000481", "status": "kesildi",
"totals": { "net": 100.00, "vat": 20.00, "grand_total": 120.00, … },
"payment": { "paid": 120.00, "remaining": 0, "status": "paid" },
…
},
"external_id": "TAH-2026-000123",
"mode": "send",
"contact_match": { "contact_id": "…", "created": true, "matched_by": "new" },
"payment": { "recorded": true, "amount": 120.00, "account_id": "12" },
"e_document_job": { "id": "a1b2…", "status": "queued" },
"warnings": [],
"view_url": "https://app.hesaptut.com/paylas/…"
},
"request_id": "…"
}- prices_include_vat: true — tahsil ettiğiniz tutar KDV dahilse; KDV hariç tutar kuruşu kuruşuna biz hesaplarız.
- expected_total — gönderirseniz fatura toplamı bununla kuruşu kuruşuna tutmalı; tutmazsa fatura kesilmez (
TOTAL_MISMATCH). Tahsil edilen para ile fatura tutarı böylece hiçbir zaman ayrışmaz. - contact.external_id — kendi müşteri numaranız. Şahısların çoğunda TCKN olmadığından eşleşme bu numarayla yapılır; aynı kişi her seferinde aynı cari kartına gider. TCKN'si olmayan şahıs için
tax_numberboş bırakılır (nihai tüketici, e-Arşiv). - e-Fatura mı e-Arşiv mi? Siz seçmezsiniz: alıcının GİB mükellefiyetine göre HesapTUT karar verir.
- payment.account_id — tahsilatın yazılacağı kasa/banka/POS (
GET /v1/accounts). Firma varsayılan tahsilat hesabı tanımladıysa göndermeniz gerekmez.
4. Çift fatura koruması (external_id)
Her yazma isteğinde kendi işlem numaranızı external_id olarak gönderin. Bağlantı koptu, cevabı alamadınız mı? Aynı isteği aynı numarayla tekrar gönderin: yeni fatura kesilmez, ilk sonuç aynen döner (idempotent_replay: true, başlık Idempotent-Replayed: true).
Aynı numarayı farklı içerikle gönderirseniz 409 EXTERNAL_ID_REUSED alırsınız. Bir faturayı sonradan numaranızla bulmak için: GET /v1/invoices?external_id=TAH-2026-000123.
5. Kesme ve GİB gönderimi
Firma size izin verirken faturanın ne kadar ilerleyeceğini seçer; siz bunu aşamazsınız (yalnız aşağı çekebilirsiniz):
- draft (yalnız taslak) — fatura taslak kalır, firma kendisi keser. İlk hafta genelde bu modla başlanır.
- issue (kes) — numara atanır, GİB'e gönderilmez.
- send (kes + gönder) — GİB'e arka planda gönderilir. Durum:
GET /v1/jobs/{id}ya daGET /v1/invoices/{id}içindekie_document.
PDF: GET /v1/invoices/{id}/pdf (taslak da olur). Müşterinize göndermek için cevaptaki view_url bağlantısını iletebilirsiniz.
6. İzinler
Firma hangi işlemlere izin vereceğini seçer. İzniniz olmayan uç 403 SCOPE_DENIED döner.
| faturalar.oku | Satış faturalarını okuma (durum, PDF, bağlantı) |
| faturalar.yaz | Satış faturası oluşturma, kesme, GİB'e gönderme, tahsilat ekleme |
| cariler.oku | Cari (müşteri/tedarikçi) kartlarını okuma |
| cariler.yaz | Cari kartı açma ve güncelleme |
| urunler.oku | Ürün/hizmet kartlarını okuma |
| urunler.yaz | Ürün/hizmet kartı açma ve güncelleme |
| hesaplar.oku | Kasa, banka ve POS hesaplarını okuma |
| ebelge.oku | Gelen e-faturaları ve mükellef sorgusunu okuma |
| teklifler.oku | Teklifleri okuma |
| teklifler.yaz | Teklif oluşturma |
| siparisler.oku | Siparişleri okuma |
| siparisler.yaz | Sipariş oluşturma, siparişten fatura |
| irsaliyeler.oku | İrsaliyeleri okuma |
| irsaliyeler.yaz | İrsaliye oluşturma, irsaliyeden fatura |
| alislar.oku | Alış faturalarını okuma |
| alislar.yaz | Alış faturası kaydetme |
| stok.oku | Depolar, stok seviyesi ve stok hareketlerini okuma |
| stok.yaz | Depolar arası transfer |
| nakit.oku | Kasa/banka hareketlerini, çek/senetleri okuma |
| nakit.yaz | Masraf, cari tahsilat/ödeme kaydetme |
| uretim.oku | Üretim iş emirlerini ve reçeteleri okuma |
| uretim.yaz | Üretim iş emri oluşturma |
| personel.oku | Personel listesi ve puantaj/PDKS kayıtlarını okuma |
| webhook.yonet | Olay bildirimi (webhook) abonelikleri |
7. Hatalar
Tüm hatalar aynı biçimdedir. code sabit ve İngilizcedir (programınız buna göre dallansın), message Türkçedir ve ne yapılacağını söyler. Destek isterken request_id'yi iletin.
{ "error": { "code": "CONTACT_CITY_REQUIRED",
"message": "Yeni cari için il zorunlu (contact.city) — e-belge adresi için gerekir.",
"details": { "field": "contact.city" } },
"request_id": "5b0e…" }| HTTP | code | Anlamı |
|---|---|---|
| 401 | MISSING_API_KEY / INVALID_API_KEY | Anahtar yok ya da yanlış. Authorization başlığını kontrol edin. |
| 401 | API_KEY_REVOKED / API_KEY_EXPIRED | Anahtar iptal edilmiş ya da süresi dolmuş — HesapTUT'tan yenisini isteyin. |
| 403 | NO_COMPANY_ACCESS | Firma size izin vermemiş (Ayarlar → Bağlı Uygulamalar). |
| 403 | SCOPE_DENIED | Firma bu işlem için izin vermemiş. |
| 400 | COMPANY_REQUIRED | Birden çok firmaya erişiminiz var: X-Company-Id başlığını gönderin. |
| 409 | EXTERNAL_ID_REUSED | Aynı external_id farklı içerikle geldi — her işleme ayrı numara. |
| 422 | VALIDATION_FAILED | Alanlar hatalı; details.fields listesi hangi alanın neden reddedildiğini söyler. |
| 422 | TOTAL_MISMATCH | expected_total tutmadı; fatura KESİLMEDİ. Fiyat/KDV/indirimi kontrol edin. |
| 422 | CONTACT_CITY_REQUIRED | Yeni cari için il (contact.city) zorunlu. |
| 422 | PAYMENT_ACCOUNT_REQUIRED | Tahsilat hesabı belli değil: payment.account_id gönderin. |
| 422 | VAT_EXEMPTION_REQUIRED | KDV %0 satır var: vat_exemption_code gönderin. |
| 429 | RATE_LIMITED / MONTHLY_QUOTA_EXCEEDED | Çok hızlı (10 sn'de 10) ya da aylık kota doldu. Retry-After başlığına bakın. |
| 503 | API_NOT_ENABLED | API bu sunucuda henüz açılmadı. |
8. Olay bildirimi (webhook)
“Fatura GİB'de kabul edildi mi?” diye sürekli sormak yerine POST /v1/webhooks ile bir adres kaydedin; olay olunca biz haber veririz. Olaylar: invoice.created, invoice.e_document_status, invoice.e_document_failed, payment.recorded. Abonelik açılınca dönen whsec_… sırrını saklayın.
Her teslimde X-HesapTUT-Signature: t=<unix>,v1=<imza> başlığı gelir. İmzayı doğrulayın ve 5 dakikadan eski t'yi reddedin:
// Node.js
const [t, v1] = req.headers["x-hesaptut-signature"].split(",").map(p => p.split("=")[1]);
const beklenen = crypto.createHmac("sha256", WHSEC).update(t + "." + hamGovde).digest("hex");
const gecerli = crypto.timingSafeEqual(Buffer.from(beklenen), Buffer.from(v1))
&& Math.abs(Date.now()/1000 - Number(t)) < 300;2xx dışı cevapta 1 dk, 5 dk, 30 dk, 2 sa, 6 sa ve 24 sa sonra yeniden denenir. Ardışık 20 başarısız teslimde abonelik durur; PATCH /v1/webhooks/{id} ile active: true gönderip yeniden açabilirsiniz. Adres https olmalı.
9. Uygulama bağlantısı (çok müşterili yazılımlar)
Birden çok HesapTUT müşterisine hizmet veriyorsanız, her müşterinin izni için bize yazmanıza gerek yok. Müşterinizi “HesapTUT ile bağlan” düğmesiyle şu adrese yönlendirin (dönüş adresinizi önce HesapTUT'a kaydettirin):
https://app.hesaptut.com/baglan?response_type=code&client_id=<bağlantı kodunuz>
&redirect_uri=<kayıtlı dönüş adresiniz>&scope=faturalar.oku%20faturalar.yaz&state=<rastgele>Firma yetkilisi izinleri görüp onaylayınca redirect_uri?code=…&state=… adresine döner (reddederse error=access_denied). state'in sizin gönderdiğinizle aynı olduğunu kontrol edin, sonra kodu 10 dakika içinde canlı anahtarınızla bozdurun:
curl -X POST https://app.hesaptut.com/api/v1/oauth/token -H "Authorization: Bearer htk_canli_…" \
-H "Content-Type: application/json" \
-d '{"grant_type":"authorization_code","code":"…","redirect_uri":"<aynı adres>"}'Cevaptaki company_id'yi saklayın ve o firma için yaptığınız her istekte X-Company-Id başlığıyla gönderin.
10. Tüm uçlar
Her ucun alanları, örnek istek ve cevapları: ayrıntılı uç başvurusu.
Uç listesi yükleniyor…