Zum Hauptinhalt springen

API — Rate-Limits & Quotas

Standard-Limits

TierCalls/MinuteCalls/StundeCalls/Tag
Standard-User601.00010.000
Power-User2005.00050.000
API-Key (Server-to-Server)60020.000200.000
Mandant gesamt1.50050.000500.000

Limits sind pro Token / Mandant, nicht IP.

Bulk-Endpoints (höhere Limits)

EndpointLimit
POST /api/*/bulk30 / Stunde, max 1000 Items / Call
POST /api/*/bulk-import5 / Stunde, max 10.000 Rows / Call
GET /api/*?include=...10 / Minute (rechenintensiv)

429-Error

{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded",
"details": {
"limit": 60,
"remaining": 0,
"resetAt": "2026-05-05T14:33:00Z",
"retryAfter": 23
}
}
}

Header:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1736888400
Retry-After: 23

Verhalten bei 429

async function callWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const res = await fetch(url, options);
if (res.status !== 429) return res;
const retryAfter = parseInt(res.headers.get("Retry-After")) || 60;
await sleep(retryAfter * 1000);
}
throw new Error("Rate-limit nach " + maxRetries + " Versuchen");
}

Bulk-Pattern statt Schleife

Anti-Pattern:

for (const customer of 1000_customers) {
await api.post("/api/customers", customer); // 1000 Calls — Rate-Limit!
}

Richtig:

await api.post("/api/customers/bulk", { items: 1000_customers });
// Ein Call, alle 1000 angelegt

Listen mit Pagination effizient holen

Anti-Pattern:

// 234 Kunden, limit=20 → 12 Calls
let page = 1;
while (true) {
const res = await api.get(`/api/customers?page=${page}`);
if (!res.data.length) break;
page++;
}

Richtig:

// Limit hochsetzen
const res = await api.get("/api/customers?limit=200");
// Bei >200: cursor verwenden

Abos / Webhooks statt Polling

Anti-Pattern: Polling alle 30 Sekunden auf Änderungen ✅ Richtig: Webhook konfigurieren — Push statt Pull

Cache-Strategien

  • Stammdaten (Kunden, Lieferanten, Produkte) — clientseitig 5-15 min cachen
  • Beleg-Detail — bei Abruf aus Cache (eigener nutzer-spezifischer Cache), bei Aktion neu laden
  • Globale Konstanten (Länder, Sprachen) — 24h cachen
  • Listen — nur cachen wenn Änderungs-Frequenz niedrig

Sehr große Exports (>10.000 Datensätze)

Statt Live-API-Call:

POST /api/exports
{
"type": "customers",
"filter": {...},
"format": "csv"
}
# Response: Job-ID

# Polling oder Webhook auf Job-Fertig:
GET /api/exports/:jobId
# Status: pending | running | done | error
# Bei done: download-URL

Quota-Erhöhung

Bei dauerhaft höherem Bedarf: Admin-Anfrage. Power-User-Tier oder Custom-Quota nach Mandanten-Vertrag.

Verwandte Doku