> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# API-Rate-Limiting: Limits, Header & 429-Backoff

> Didit API-Rate-Limits: X-RateLimit-Header, Retry-After-Backoff, Endpoint-Scopes und 429-Handling für reibungslosen Produktionsbetrieb.

Didit setzt mehrere Ebenen von Rate-Limits ein, um die API stabil zu halten und gleichzeitig normale Lastspitzen zu ermöglichen. Limits werden **pro Kennung** angewendet – Ihr `x-api-key` oder, falls kein API-Schlüssel gesendet wird, Ihre Client-IP. Jeder Gültigkeitsbereich verfügt über einen unabhängigen Zähler mit einem gleitenden 60-Sekunden-Fenster.

## Kennung und Ausnahmen

Die Middleware wählt eine Kennung für das Rate-Limit in dieser Reihenfolge:

1. `x-api-key`-Header → Zähler auf Schlüssel-Ebene.
2. Erster Hop in `X-Forwarded-For` → Zähler auf IP-Ebene.
3. `REMOTE_ADDR` → Zähler auf IP-Ebene.

Die folgenden Aufrufe **umgehen** das Rate-Limiting vollständig:

* Der interne `DIDIT_WEBAPP_API_KEY` (ausschließlich von der Didit-Konsole verwendet).
* Endpoints, die durch die Berechtigung `IsValidSession` geschützt sind (der Traffic von eingebetteten Sitzungen/SDK-Sitzungen).
* `GET /system/healthcheck` (wird von `HealthMiddleware` vor diesem Stack verarbeitet).
* Provider-Webhooks: Stripe, AML-Screening, Callbacks zur Gesichtsverifizierung, Änderungen von KYC-Listen.

## Globale Limits

Jede authentifizierte Anfrage wird außerdem nach HTTP-Methode in einen generischen Gültigkeitsbereich eingestuft, sodass jede neue Route sinnvolle Standardwerte erbt:

| Gültigkeitsbereich | HTTP-Methode(n) | Limit | Fenster |
| - | - | - | - |
| `generic-get` | `GET` | 600 / Min. | 60 s |
| `generic-write` | `POST`, `PATCH`, `DELETE` | 300 / Min. | 60 s |
| `ingest-write` | `POST /v3/transactions/`, `POST /v3/events/`, `POST /v3/events/batch/` | 600 / Min. | 60 s |

Das Erstellen von Transaktionen und die Event-Erfassung zählen zu `ingest-write` **anstelle von** `generic-write`: Eine Integration mit hohem Erfassungsvolumen erhält 600 Anfragen pro Minute pro API-Schlüssel, ohne das Budget ihrer anderen Schreibvorgänge aufzubrauchen.

Endpoint-spezifische Limits (nächster Abschnitt) gelten **zusätzlich** zum globalen Limit. Der erste Gültigkeitsbereich, der seinen Zähler überschreitet, gibt den Status `429` zurück.

## Endpoint-spezifische Limits

Die Middleware definiert strengere Gültigkeitsbereiche für die Routen mit den höchsten Auswirkungen:

| Gültigkeitsbereich | Endpoint(s) | Limit | Quellkonstante |
| - | - | - | - |
| `session-v2-create` | `POST /v2/session/`, `POST /v3/session/` | 600 / Min. | `SESSION_CREATE_RATE_LIMIT` |
| `session-decision` | `GET /v1/session/<id>/decision/`, `GET /v2/session/<id>/decision/` | 100 / Min. | `SESSION_DECISION_RATE_LIMIT` |
| `session-generate-pdf` | `GET /v1/session/<id>/generate-pdf/`, `GET /v3/session/<id>/generate-pdf/` | 50 / Min. | `SESSION_PDF_RATE_LIMIT` |
| `session-add-images` | `POST`/`PATCH /session/<id>/add-images/` | 10 / Min. | `SESSION_ADD_IMAGES_RATE_LIMIT` |
| `session-update-kyc` | `POST`/`PATCH /session/<id>/update-data/` | 10 / Min. | `SESSION_UPDATE_KYC_RATE_LIMIT` |
| `session-update-poa` | `POST`/`PATCH /session/<id>/update-poa-data/` | 10 / Min. | `SESSION_UPDATE_POA_RATE_LIMIT` |

> [!NOTE]
>
> `GET /v3/session/<id>/decision/` wird von `SessionV3RetrieveView` bereitgestellt und befindet sich **nicht** im Gültigkeitsbereich `session-decision`. Er unterliegt ausschließlich der Obergrenze von 600/Min. von `generic-get`. Die Drosselung von 100/Min. für Entscheidungen gilt für die Entscheidungspfade von v1 und v2.

> [!WARNING]
>
> Die Codebasis definiert außerdem `FREE_SESSION_RATE_LIMIT = 10` und `PAID_SESSION_RATE_LIMIT = 600`. Diese Konstanten werden **derzeit nicht** auf `POST /v3/session/` angewendet – dieser Endpoint verwendet das einheitliche `session-v2-create`-Limit von 600/Min. für jede Anwendung. Planen Sie keine Kapazitäten basierend auf einer Drosselung für das kostenlose Kontingent pro Workflow, solange dies nicht als aktiv dokumentiert ist.

## Format der 429-Antwort

Wenn Sie einen Gültigkeitsbereich überschreiten, erhalten Sie den HTTP-Status `429` mit folgendem Body und folgenden Headern:

```http theme={null}
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1762534870
Retry-After: 37

{
  "detail": "Session creation rate limit exceeded. You can make up to 600 requests per minute."
}
```

| Header | Bedeutung |
| - | - |
| `X-RateLimit-Limit` | Maximal zulässige Anfragen im aktuellen Fenster für den Gültigkeitsbereich, dessen Limit überschritten wurde. |
| `X-RateLimit-Remaining` | Verbleibende Anfragen, bevor das Fenster zurückgesetzt wird. Wird auf `0` fixiert, sobald Sie das Limit überschreiten. |
| `X-RateLimit-Reset` | Unix-Epochenzeit in Sekunden, zu der der Zähler auf null zurückgesetzt wird. |
| `Retry-After` | Sekunden, die vor dem nächsten Versuch gewartet werden müssen, aufgerundet, sodass die Wartezeit immer im nächsten Fenster landet. **Nur bei `429`-Antworten** vorhanden, nicht bei jeder Anfrage. |

`X-RateLimit-*`-Header werden nur bei der `429`-Antwort ausgegeben – erfolgreiche Antworten enthalten sie derzeit nicht. Verwenden Sie den Wert von `Retry-After` (oder `X-RateLimit-Reset - now`) als Verzögerung vor dem nächsten Versuch.

## Empfehlungen für Clients

* **Beachten Sie `Retry-After`** als Mindestwert für Ihr Backoff. Nutzen Sie darauf aufbauend ein exponentielles Backoff mit Jitter (`5s → 10s → 20s → 40s ± random`), damit sich zwei Clients, die gleichzeitig auf einen `429` stoßen, nicht synchronisieren.
* **Brechen Sie Wiederholungsversuche** nach einer geringen Obergrenze (z. B. 5 Versuchen) ab. Leiten Sie den Fehler an Ihren Aufrufer weiter und benachrichtigen Sie Ihr Team – anhaltende 429-Fehler weisen in der Regel auf eine Endlosschleife hin, nicht auf eine vorübergehende Spitze.
* **Verwenden Sie einen Bezeichner pro Workload.** Wenn Sie einen `x-api-key` über mehrere Backends hinweg teilen, fallen diese in denselben Zähler, was dazu führen kann, dass ein störender Nachbar die übrigen ausbremst. Erstellen Sie eine separate Anwendung (und somit einen separaten `api_key`) pro Umgebung, Marke oder Kunde – siehe [Programmatische Registrierung](https://docs.didit.me/de/integration/programmatic-registration.md#anwendungen-verwalten).
* **Cachen Sie Entscheidungsabfragen.** Die `/decision/`-Endpoints sind nicht für enge Polling-Schleifen ausgelegt; wenn Sie Echtzeit-Updates benötigen, nutzen Sie [Webhooks](https://docs.didit.me/de/integration/webhooks.md) und rufen Sie `GET /v3/session/<id>/decision/` nur für Backfills oder Audits auf.
* **Generieren Sie PDFs offline.** Das Rendern von PDFs ist CPU-intensiv (Begrenzung auf 50/Min.). Lösen Sie dies über eine Warteschlange aus, nachdem die Sitzung einen finalen Status erreicht hat, und nicht bei jedem Seitenaufruf.

## Beispiel für einen 429-kompatiblen Client

```javascript theme={null}
async function postSession(body) {
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch("https://verification.didit.me/v3/session/", {
      method: "POST",
      headers: {
        "x-api-key": process.env.DIDIT_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });

    if (res.status !== 429) return res;

    const retryAfter = Number(res.headers.get("Retry-After") ?? "1");
    const jitter = Math.random() * 1000;
    await new Promise((r) => setTimeout(r, retryAfter * 1000 + jitter));
  }
  throw new Error("Didit: too many 429s, giving up");
}
```

```python theme={null}
import os
import random
import time

import httpx

def post_session(body: dict) -> httpx.Response:
    with httpx.Client(timeout=15) as client:
        for _ in range(5):
            resp = client.post(
                "https://verification.didit.me/v3/session/",
                headers={
                    "x-api-key": os.environ["DIDIT_API_KEY"],
                    "Content-Type": "application/json",
                },
                json=body,
            )
            if resp.status_code != 429:
                return resp
            wait_s = float(resp.headers.get("Retry-After", "1")) + random.random()
            time.sleep(wait_s)
    raise RuntimeError("Didit: too many 429s, giving up")
```

## Benötigen Sie mehr Spielraum?

Wenn 600 Sitzungen pro Minute nicht ausreichen – beispielsweise bei einem Marketing-Launch oder einem Backfill –, senden Sie eine E-Mail an [support@didit.me](mailto:support@didit.me) mit der UUID Ihrer `application` sowie den erwarteten Dauer- und Spitzenraten. Ausnahmen werden pro Anwendung konfiguriert.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.