Paraşüt entegrasyonunda çoğu sorun ilk istekte değil, üretimde ortaya çıkar: süresi dolmuş bir token, yetkisi olmayan bir kullanıcı, eksik bir zorunlu alan ya da yoğun saatte dolan istek limiti. Bu durumların her biri farklı bir HTTP durum koduyla gelir ve her birine farklı tepki vermeniz gerekir. Yanlış tepki, örneğin 403'ü sürekli tekrar denemek, istemcinizin engellenmesine kadar gidebilir.

Bu yazıda Paraşüt API'nin hata gövdesi biçimini, belgelenmiş durum kodlarını ve her biri için ne yapmanız gerektiğini, rate limit ve 429 davranışını ve Paraşüt'ün resmi örnek uygulamasından uyarlanmış bir tekrar deneme katmanını anlatıyoruz. Token tarafını OAuth2 token alma yazımızda, genel mimariyi Paraşüt entegrasyonu rehberinde bulabilirsiniz.

Hata gövdesinin biçimi: errors dizisi

Hata gövdesinin biçimi: errors dizisi: yanıtında errors dizisi nesne değil metin içerir. Bu kod, firmanın Paraşüt kullanım süresinin sona erdiğini..., Geçerli JSON olmayan bir...

Paraşüt hata yanıtları JSON:API biçimindedir. Gövdede bir errors dizisi bulunur ve her eleman şu alanları taşıyabilir:

AlanAnlamı
titleKısa hata başlığı
detailAyrıntılı açıklama
codeHata kodu; yalnızca bazı hatalarda döner
source.pointerHatalı gövde alanı, örneğin /data/attributes/uri
source.parameterHatalı sorgu parametresi
metaHataya özel ek bilgi, örneğin mevcut faturanın sales_invoice_id değeri
json
{
  "errors": [
    {
      "title": "must be https",
      "detail": "Uri must be https",
      "source": { "pointer": "/data/attributes/uri" }
    }
  ]
}

Paraşüt'ün kendi belgesi şunu vurgular: hata mesajları zamanla değişebilir; hatayı programatik olarak ayırt etmek için HTTP durum kodunu ve source.pointer değerini kullanın. Mesajların çoğu İngilizcedir, satış faturası gibi bazı uçlarda Türkçedir; kural ikisinde de aynıdır. Metni kullanıcıya göstermek ve loglamak için kullanın, ama if koşullarını mesaj metnine bağlamayın.

İki istisna: 402 ve bozuk JSON

Hata ayrıştırıcınız iki durumu daha kabul etmeli. Webhook uçlarında belgelenmiş olsalar da Paraşüt'ün kuralı, ayrıştırıcının bunları her yerde kabul etmesidir:

  • 402 yanıtında errors dizisi nesne değil metin içerir. Bu kod, firmanın Paraşüt kullanım süresinin sona erdiğini gösterir.
  • Geçerli JSON olmayan bir gövde uca ulaşmadan 400 ile reddedilir ve yanıt JSON:API değildir (Content-Type: application/json).
json
{ "errors": ["Paraşüt kullanım süreniz sona ermiş."] }

{ "error": "Invalid JSON" }

Üç biçimi de güvenle okuyan küçük bir yardımcı:

javascript
export function parseErrors(body) {
  if (!body || typeof body !== 'object') return [];
  if (typeof body.error === 'string') return [{ title: body.error }]; // bozuk JSON (400)
  if (!Array.isArray(body.errors)) return [];
  return body.errors.map((e) =>
    typeof e === 'string'
      ? { title: e } // 402: metin dizisi
      : { title: e.title, detail: e.detail, code: e.code, pointer: e.source?.pointer,
          parameter: e.source?.parameter, meta: e.meta },
  );
}

Durum kodları ve doğru tepki

Aşağıdaki tablo Paraşüt'ün resmi kural dosyasındaki hata tablosunu esas alır:

KodAnlamıNe yapmalı
200 / 201Başarılı / oluşturulduYanıtı işleyin
202İş kuyruğa alındı (e-Fatura, e-Arşiv, resend, respond, legalize ve benzeri)Başarı değil; data.id ile trackable_jobs ucunu sorgulayın
204Gövdesiz başarı (çoğu DELETE)Gövdeyi ayrıştırmayın
304match ucunda eşleşecek kayıt yokHata değil
400Eksik veya hatalı gövde ya da parametre; bilinmeyen filtre, sıralama veya includeİsteği düzeltin, tekrar denemeyin
401Token geçersiz veya süresi dolmuş; kullanıcının bu company_id için erişimi yokBir kez yenileyin, bir kez tekrarlayın; yine 401 ise durun
402Firmanın Paraşüt kullanım süresi sona ermişKullanıcıya bildirin, tekrar denemeyin
403Entegrasyon izni ya da rol yok; pakette özellik yok; istemcinin firmaya erişimi yokTekrar denemeyin; title değerini loglayın
404Kayıt yokKaydı yerelde işaretleyin
409Kategoriden öğe çıkarmada kayıt zaten değişmişKaydı yeniden okuyun
422Doğrulama hatasısource.pointer ile alanı bulun, meta değerini okuyun
429İstek limiti dolduYanıttaki süre kadar bekleyin
500 / 503Paraşüt tarafında geçici sorunSınırlı sayıda, üstel geri çekilmeyle tekrar deneyin

401: bir kez yenile, bir kez tekrarla

401 genellikle access token'ın süresinin dolduğu anlamına gelir. Doğru tepki, token'ı bir kez yenileyip isteği bir kez tekrarlamaktır. Tekrar yine 401 dönerse durun ve kullanıcıya hesabı yeniden bağlamasını söyleyin; sonsuz yenileme döngüsüne girmeyin. 401'in başka nedenleri de vardır: kullanıcının o company_id için erişimi olmayabilir. Ayrıca DELETE /payments/{id} silinemeyen bir ödemede de 401 döner; fatura tahsilatını geri almanın doğru yolu DELETE /transactions/{id} çağrısıdır (tahsilat yazımızda ayrıntılı).

403: asla otomatik tekrar denemeyin

403 bir yetki ya da paket sorunudur ve beklemekle düzelmez. Kullanıcıya onay alınan token, kullanıcının o firmadaki rolüyle sınırlıdır; rolün kapsamadığı işlemler 403 döner. Webhook uçlarında belgelenmiş 403 başlıkları neden sorununu iyi gösterir: API client's package does not include webhooks, Webhooks are only available to Developer Portal API clients, User has no 3rd party integration permission for company: 123, API client has no access to company: 123. Uygulamanız bu durumda kullanıcıya anlaşılır bir mesaj göstermeli ve isteği tekrarlamamalıdır.

404: kayıt yok

Silinmiş ya da başka firmaya ait bir kaydı istediğinizde 404 alırsınız. Bazı uçlarda 404 gövdesizdir: örneğin sales_invoices/find_by_external_reference_key anahtar bulunamazsa gövdesiz 404 döner, bu da "bu anahtarla fatura yok" demektir. Webhook uçlarında başka bir istemcinin webhook'unu istemek de 404 döner.

422: doğrulama hataları ve belgelenmiş örnekler

422, isteğin biçim olarak doğru ama içerik olarak geçersiz olduğunu söyler. source.pointer değerini formunuzdaki alana eşleyerek kullanıcıya doğru alanı gösterebilirsiniz. Belgelerde geçen bazı örnekler:

DurumBelgelenmiş yanıt
Satış faturasında external_reference_key UUID değilGönderilen key geçerli bir UUID formatında değil.
Aynı anahtarla ikinci faturaBu key ile daha önce bir fatura oluşturulmuş. (mevcut fatura errors[].meta.sales_invoice_id içinde)
Webhook uri HTTPS değilsource.pointer: /data/attributes/uri
Aynı model ve action için ikinci webhook422
Bir kayda 120'den fazla yorumMaksimum 120 yorum sayısına ulaşıldı.
comments ve inbound_e_despatches uçlarında page[size] 25'ten büyük422
Gelen e-Faturadan ikinci kez fiş oluşturma422

Özellikle idempotent fatura senaryosunda 422 bir başarı işaretidir: zaman aşımından sonra aynı anahtarla tekrar denediğinizde fatura zaten oluşmuşsa, mevcut faturanın kimliğini mesaj metninden değil errors[].meta.sales_invoice_id alanından alırsınız. Ayrıntılar fatura kesme yazımızda.

Rate limit: paket limitleri ve 429

Rate limit: paket limitleri ve 429

Paraşüt'ün belgesine göre rate limit, başvurduğunuz pakete göre değişir. Paketiniz 10 saniyede kaç istek yapabileceğinizi, bir ayda toplam kaç istek yapabileceğinizi ve hangi firmalar için işlem yapabileceğinizi belirler. Limit dolduğunda HTTP 429 alırsınız ve yanıtta ne zaman tekrar istek yapabileceğinize dair süre bilgisi bulunur. Paket bazlı sayıları API belgesi vermez; Paraşüt'ün resmi örnek uygulaması ise rate limit senaryosunu 10 saniyede 10 istek sınırıyla gösterir.

Kendi paketinizin limitlerini ve kapsamını DevHub'daki API bilgileri ve başvuru sayfalarından kontrol edin. Webhook kullanmak istiyorsanız paketinizin webhook içermesi gerekir; içermiyorsa webhook uçları 403 döner (webhook kurulumu yazımız).

İstek atmadan önce kendiniz yavaşlayın

429'u tamamen engellemek mümkün olmasa da sık görmek tasarım hatasıdır. Toplu eşitleme işlerinde istekleri paket limitinizin altında tutan basit bir kayan pencere kullanabilirsiniz. Genel stratejiler için API rate limiting stratejileri yazımıza bakabilirsiniz.

javascript
// Varsayılan: 10 saniyede en fazla 10 istek; paketinize göre PARASUT_RATE_LIMIT ile değiştirin
const WINDOW_MS = 10_000;
const LIMIT = Number(process.env.PARASUT_RATE_LIMIT ?? 10);
const sent = [];

export async function throttle() {
  for (;;) {
    const now = Date.now();
    while (sent.length && now - sent[0] >= WINDOW_MS) sent.shift();
    if (sent.length < LIMIT) { sent.push(now); return; }
    await sleep(WINDOW_MS - (now - sent[0]) + 50);
  }
}

Tekrar deneme katmanı: örnek uygulamadan uyarlama

Aşağıdaki fonksiyon resmi örnek uygulamadaki parasut.ts dosyasının 429 mantığını (en fazla 3 tekrar, Retry-After okuma) temel alır ve Paraşüt kurallarındaki 401, 403 ve 5xx davranışlarını ekler. 5xx tekrarı yalnızca GET gibi idempotent isteklerde ya da idempotans anahtarlı POST'ta yapılır; aksi hâlde aynı kaydı iki kez oluşturabilirsiniz.

javascript
const API = 'https://api.parasut.com/v4';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

function retryAfterMs(res) {
  const v = res.headers.get('retry-after');
  const s = Number(v);
  if (v && Number.isFinite(s)) return Math.max(s, 1) * 1000;
  const d = v ? Date.parse(v) : NaN;
  if (Number.isFinite(d)) return Math.max(d - Date.now(), 1000);
  return 10_000; // pencere uzunluğu
}

export async function parasut(path, { method = 'GET', json, idempotent = method === 'GET' } = {}) {
  let refreshed = false;
  for (let attempt = 0; ; attempt++) {
    await throttle();
    const res = await fetch(API + path, {
      method,
      headers: {
        Authorization: `Bearer ${await getAccessToken(store)}`,
        Accept: 'application/json',
        ...(json ? { 'Content-Type': 'application/vnd.api+json' } : {}),
      },
      body: json ? JSON.stringify(json) : undefined,
    });

    if (res.status === 401 && !refreshed) { refreshed = true; await forceRefresh(store); continue; }
    if (res.status === 429 && attempt < 3) { await sleep(retryAfterMs(res)); continue; }
    if ((res.status === 500 || res.status === 503) && idempotent && attempt < 3) {
      await sleep(2 ** attempt * 1000 + Math.random() * 500); // üstel geri çekilme + rastgele gecikme
      continue;
    }
    // 400, 402, 403, 404, 422: tekrar denenmez
    if (res.status === 204 || res.status === 304) return { status: res.status };
    const text = await res.text();
    const body = text ? JSON.parse(text) : undefined;
    if (!res.ok) {
      const err = new Error(`Paraşüt HTTP ${res.status}`);
      Object.assign(err, { status: res.status, errors: parseErrors(body) });
      throw err;
    }
    return { status: res.status, body };
  }
}

getAccessToken ve kilitli yenileme için OAuth2 yazımızdaki kodu kullanabilirsiniz; forceRefresh aynı kilidi kullanan ve süre kontrolü yapmadan yenileyen bir sürümdür. Bu katmanın üstünde 403 ve 402'yi kullanıcı arayüzüne, 422'yi form doğrulamasına, 404'ü yerel kaydın işaretlenmesine yönlendirin.

202 ve trackable job: hata değil ama başarı da değil

e-Fatura ve e-Arşiv oluşturma istekleri 202 döner ve data bir trackable job'dır. Trackable job, Paraşüt'ün arka planda yürüttüğü işin durumunu izlediğiniz kayıttır. GET /{company_id}/trackable_jobs/{id} ile status değeri done ya da error olana kadar sorgularsınız. 202 dönen bir iş de error ile bitebilir ve hatalar attributes.errors içinde metin dizisi olarak gelir. Ayrıntılar e-Fatura ve e-Arşiv yazımızda.

Sık Sorulan Sorular

Paraşüt API 429 hatası alırsam ne kadar beklemeliyim?

Yanıtta bekleme süresi bilgisi bulunur, ancak alan adı spec'te belgelenmemiştir. Resmi örnek uygulama Retry-After başlığına bakar, yoksa 10 saniye bekler. Süre okunamazsa üstel geri çekilme ve rastgele gecikme kullanın.

403 alan bir isteği tekrar denemeli miyim?

Hayır. 403 yetki ya da paket sorunudur. İsteği tekrarlamak sonucu değiştirmez ve fazla sayıda 403, istemcinizin engellenmesine yol açabilir. title değerini loglayın ve kullanıcıya yetki ya da paket sorununu gösterin.

402 hatası ne anlama gelir?

Firmanın Paraşüt kullanım süresi sona ermiştir. Bu yanıtta errors dizisi nesne yerine metin içerir. Kullanıcıya bildirin ve tekrar denemeyin.

Hata mesajına göre karar verebilir miyim?

Önerilmez. Paraşüt, mesajların zamanla değişebileceğini belirtir; kararları durum koduna, source.pointer ve gerektiğinde meta alanına bağlayın.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Paraşüt entegrasyonu genel rehberi · Paraşüt OAuth2 token alma · Paraşüt API filtreleme ve sayfalama · Paraşüt API ile fatura kesme · API rate limiting stratejileri · Node.js REST API geliştirme