API — Rate-Limits & Quotas
Standard-Limits
| Tier | Calls/Minute | Calls/Stunde | Calls/Tag |
|---|---|---|---|
| Standard-User | 60 | 1.000 | 10.000 |
| Power-User | 200 | 5.000 | 50.000 |
| API-Key (Server-to-Server) | 600 | 20.000 | 200.000 |
| Mandant gesamt | 1.500 | 50.000 | 500.000 |
Limits sind pro Token / Mandant, nicht IP.
Bulk-Endpoints (höhere Limits)
| Endpoint | Limit |
|---|---|
POST /api/*/bulk | 30 / Stunde, max 1000 Items / Call |
POST /api/*/bulk-import | 5 / 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
- CRUD-Patterns — Pagination, Bulk
- Webhooks — Push statt Pull
- Sync und Jobs