HesapTUT

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_number boş 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 da GET /v1/invoices/{id} içindeki e_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.okuSatış faturalarını okuma (durum, PDF, bağlantı)
faturalar.yazSatış faturası oluşturma, kesme, GİB'e gönderme, tahsilat ekleme
cariler.okuCari (müşteri/tedarikçi) kartlarını okuma
cariler.yazCari 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.okuKasa, banka ve POS hesaplarını okuma
ebelge.okuGelen e-faturaları ve mükellef sorgusunu okuma
teklifler.okuTeklifleri okuma
teklifler.yazTeklif oluşturma
siparisler.okuSiparişleri okuma
siparisler.yazSipariş oluşturma, siparişten fatura
irsaliyeler.okuİrsaliyeleri okuma
irsaliyeler.yazİrsaliye oluşturma, irsaliyeden fatura
alislar.okuAlış faturalarını okuma
alislar.yazAlış faturası kaydetme
stok.okuDepolar, stok seviyesi ve stok hareketlerini okuma
stok.yazDepolar arası transfer
nakit.okuKasa/banka hareketlerini, çek/senetleri okuma
nakit.yazMasraf, cari tahsilat/ödeme kaydetme
uretim.okuÜretim iş emirlerini ve reçeteleri okuma
uretim.yazÜretim iş emri oluşturma
personel.okuPersonel listesi ve puantaj/PDKS kayıtlarını okuma
webhook.yonetOlay 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…" }
HTTPcodeAnlamı
401MISSING_API_KEY / INVALID_API_KEYAnahtar yok ya da yanlış. Authorization başlığını kontrol edin.
401API_KEY_REVOKED / API_KEY_EXPIREDAnahtar iptal edilmiş ya da süresi dolmuş — HesapTUT'tan yenisini isteyin.
403NO_COMPANY_ACCESSFirma size izin vermemiş (Ayarlar → Bağlı Uygulamalar).
403SCOPE_DENIEDFirma bu işlem için izin vermemiş.
400COMPANY_REQUIREDBirden çok firmaya erişiminiz var: X-Company-Id başlığını gönderin.
409EXTERNAL_ID_REUSEDAynı external_id farklı içerikle geldi — her işleme ayrı numara.
422VALIDATION_FAILEDAlanlar hatalı; details.fields listesi hangi alanın neden reddedildiğini söyler.
422TOTAL_MISMATCHexpected_total tutmadı; fatura KESİLMEDİ. Fiyat/KDV/indirimi kontrol edin.
422CONTACT_CITY_REQUIREDYeni cari için il (contact.city) zorunlu.
422PAYMENT_ACCOUNT_REQUIREDTahsilat hesabı belli değil: payment.account_id gönderin.
422VAT_EXEMPTION_REQUIREDKDV %0 satır var: vat_exemption_code gönderin.
429RATE_LIMITED / MONTHLY_QUOTA_EXCEEDEDÇok hızlı (10 sn'de 10) ya da aylık kota doldu. Retry-After başlığına bakın.
503API_NOT_ENABLEDAPI 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…

HesapTUT API Kılavuzu | HesapTUT