Dokumentation

Ratenbegrenzungen & Fehler

Aktuelle Tarifkontingente, Anfragelimits pro Minute, 429-Verhalten und Backend-Fehlercodes.


Kontingent nach Tarif

Monatliche Extraktionskontingente werden am ersten Tag jedes UTC-Monats zurueckgesetzt. Das Kontingent wird gezaehlt, nachdem eine erfolgreiche Extraktion finalisiert und gespeichert wurde.

TarifMonatliche ExtraktionenAnfragelimitLocale-HinweisePro-Funktionen
Free10010 req/minnur autoNein
Hobby50060 req/minauto, en, de, fr, es, nlNein
Starter2,000300 req/minauto, en, de, fr, es, nlNein
Pro10,000600 req/minauto, en, de, fr, es, nlJa
Business50,0001200 req/minauto, en, de, fr, es, nlJa

Pro-Funktionen sind ZUGFeRD-Verarbeitung, DATEV-Export, Duplikaterkennung, Anomalieerkennung, Kreditoren-Analysen und Feld-Bounding-Boxes.

Umgang mit 429

Die API kann aus zwei Gruenden 429 Too Many Requests zurueckgeben:

FehlercodeBedeutungRetry-Header
rate_limit_exceededDas gleitende Minutenlimit fuer API-Schluessel oder Dashboard-Nutzer wurde ueberschrittenRetry-After: 60
quota_exceededDas monatliche Extraktionskontingent des Tarifs ist erschoepftRetry-After enthaelt den ersten Tag des naechsten UTC-Monats

Nutzen Sie exponentielles Backoff fuer rate_limit_exceeded. Stoppen Sie Extraktionstraffic oder wechseln Sie den Tarif bei quota_exceeded.

import random
import time

import httpx


def extract_with_retry(text: str, api_key: str, max_retries: int = 5) -> dict:
    delay = 1.0
    for _ in range(max_retries):
        response = httpx.post(
            "https://api.parsurai.com/v1/extract",
            headers={"X-API-Key": api_key},
            json={"text": text, "locale": "auto"},
        )
        if response.status_code != 429:
            response.raise_for_status()
            return response.json()

        body = response.json()
        if body.get("error", {}).get("code") == "quota_exceeded":
            raise RuntimeError(body["error"]["message"])

        retry_after = int(response.headers.get("Retry-After", "60"))
        time.sleep(retry_after + random.uniform(0, delay))
        delay = min(delay * 2, 60)

    raise RuntimeError("Max retries exceeded")
async function extractWithRetry(text, apiKey, maxRetries = 5) {
  let delay = 1000;

  for (let attempt = 0; attempt < maxRetries; attempt += 1) {
    const res = await fetch('https://api.parsurai.com/v1/extract', {
      method: 'POST',
      headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
      body: JSON.stringify({ text, locale: 'auto' }),
    });

    if (res.status !== 429) {
      if (!res.ok) throw new Error(`API error: ${res.status}`);
      return res.json();
    }

    const body = await res.json();
    if (body.error?.code === 'quota_exceeded') {
      throw new Error(body.error.message);
    }

    const retryAfter = Number(res.headers.get('Retry-After') ?? '60') * 1000;
    await new Promise((resolve) => setTimeout(resolve, retryAfter + Math.random() * delay));
    delay = Math.min(delay * 2, 60000);
  }

  throw new Error('Max retries exceeded');
}

Umgang mit 503 und 504

Zwei weitere Antworten beschreiben den Dienst statt Ihrer Anfrage. Beide koennen gefahrlos wiederholt werden:

FehlercodeStatusBedeutung
extraction_capacity_exhausted503Alle PDF-Extraktionsplaetze des Prozesses sind belegt. Es wurde keine Arbeit begonnen, ein erneuter Versuch kostet also nichts.
pdf_extraction_timeout504Die Textextraktion hat ihr 20-Sekunden-Budget ueberschritten.

Keine der beiden enthaelt einen Retry-After-Header. Nutzen Sie dasselbe exponentielle Backoff wie oben, beginnend bei etwa einer Sekunde. Wiederholen Sie ein 504 nur wenige Male: Ein Dokument, das wiederholt in den Timeout laeuft, ist fuer diesen Endpunkt zu teuer -- teilen Sie es auf.

Fehlerformat

Alle Anwendungsfehler verwenden dieselbe JSON-Huelle:

{
  "error": {
    "code": "quota_exceeded",
    "message": "Monatliches Extraktionskontingent ueberschritten. Bitte upgraden Sie Ihren Plan, um fortzufahren.",
    "upgrade_url": "https://invoice-extractor.com/pricing"
  }
}

upgrade_url ist nur bei Kontingent- und Tarifgating-Fehlern vorhanden.

Vollstaendige Fehlercode-Referenz

CodeHTTP StatusDescription
invalid_api_key401Ungueltiger oder inaktiver API-Schluessel
inactive_subscription402Abonnement ist inaktiv
feature_not_in_plan402Funktion erfordert einen hoeheren Tarif
quota_exceeded429Monatliches Extraktionskontingent erschoepft
rate_limit_exceeded429Gleitendes Zeitfenster fuer Anfragen ueberschritten
validation_error422Anfragevalidierung fehlgeschlagen
extraction_failed502Fehler im Extraktionsdienst
upload_too_large413Hochgeladene Datei ueberschreitet das Limit dieses Endpunkts: 25 MB fuer Extraktion und Split, 10 MB fuer Klassifizierung und Zuschnitt, 5 MB fuer die oeffentliche Demo. Jeder zu grosse Upload liefert diesen einen Code.
artifact_too_large413AP-Workflow-Artefakt ueberschreitet das Limit von 25 MB
upload_not_authorized422Der Upload konnte nicht autorisiert werden. Angegebener Dateityp oder angegebene Groesse liegen ausserhalb dessen, was dieser Endpunkt akzeptiert; es wurden keine Daten gesendet.
pdf_too_many_pages422PDF ueberschreitet das Seitenlimit dieses Pfads: 30 fuer Standard- und Demo-Extraktion, 10 fuer Pro AP, bis zu 100 fuer Business AP. Kein Endpunkt darf die Obergrenze von 100 Seiten ueberschreiten.
unsupported_einvoice_profile422Wohlgeformtes E-Rechnungs-XML mit nicht unterstuetztem UBL/CII-Profil
pdf_unreadable422PDF ist beschaedigt, verschluesselt oder passwortgeschuetzt
image_too_large_to_process422Bild ueberschreitet das Dekodierlimit von 100 Megapixeln bei /v1/documents/crop
einvoice_attachment_too_large422Eingebetteter E-Rechnungs-Anhang ueberschreitet 10 MB
extraction_capacity_exhausted503Extraktionskapazitaet ist momentan ausgelastet; mit Backoff wiederholen
pdf_extraction_timeout504PDF-Textextraktion hat das 20-Sekunden-Budget ueberschritten; mit Backoff wiederholen
pdf_no_extractable_text422Gescanntes PDF ohne Textebene, das auch OCR nicht lesen konnte; Seite als Bild hochladen
ocr_unavailable503Texterkennung war fuer ein gescanntes PDF noetig und ist momentan nicht verfuegbar; mit Backoff wiederholen
legal_reacceptance_required428Aktualisierte AGB oder Datenschutzerklaerung muessen vom Kontoinhaber akzeptiert werden, bevor API-Aufrufe fortgesetzt werden
email_verification_required428Die E-Mail-Adresse muss vom Kontoinhaber bestaetigt werden, bevor API-Aufrufe fortgesetzt werden
internal_error500Unerwarteter Serverfehler

Monitoring-Empfehlungen

  • Fragen Sie GET /v1/usage ab, um calls_this_month, quota, plan_name und resets_on zu lesen.
  • Aktivieren Sie Kontingentwarnungen unter Dashboard > Einstellungen > Benachrichtigungen.
  • Alarmieren Sie, wenn calls_this_month / quota Ihren internen Grenzwert ueberschreitet.

Entscheidungen bei Tarif-Gating

Einige Anfragen sind syntaktisch gueltig, werden aber vom aktuellen Tarif blockiert. Diese Antworten verwenden 402 mit feature_not_in_plan und verbrauchen kein Extraktionskontingent.

FunktionMindesttarifHinweise
Explizite Locale-Hinweise wie de, fr, es oder nlHobbyFree sollte locale: "auto" senden
ZUGFeRD-VerarbeitungProPOST /v1/extract/zugferd
Duplikat- und AnomaliepruefungPro/v1/intelligence/*
Kreditoren-AnalysenProGET /v1/analytics/vendors
DATEV-ExportProPOST /v1/exports/datev
Feld-Bounding-BoxesProNur bei PDF-/Bild-Uploads, wenn OCR-Spans verfuegbar sind

Usage-Antwortbeispiel

curl https://api.parsurai.com/v1/usage \
  -H "X-API-Key: $PARSURAI_API_KEY"
{
  "calls_this_month": 318,
  "quota": 2000,
  "plan_name": "starter",
  "resets_on": "2026-07-01"
}