---
seo_title: Watch (Phase 2): Website-Änderungserkennung | EnConvert
meta_desc: Private Beta, Phase 2: Überwache jede URL stündlich oder seltener auf Änderungen, mit Webhook oder E-Mail an dich, sobald sich der Seiteninhalt wirklich ändert.
keywords: website änderungen überwachen api, webhook für seitenänderungen einrichten, api zur seitenüberwachung, preisänderung automatisch erkennen api, url auf änderungen prüfen api, website diff api, seiteninhalt überwachen webhook, change detection api für websites
---

# Website-Änderungserkennung API

<div class="alert alert-warning">
<strong>Private Beta.</strong> Watch ist heute mit deinem normalen API-Schlüssel in jedem kostenpflichtigen Tarif aufrufbar, und Watcher kosten keine Ops; im kostenlosen Founding-Tarif lassen sie sich gar nicht anlegen. Angekündigt oder allgemein verfügbar ist Watch nicht: Request- und Response-Formen können sich jederzeit ohne Vorankündigung ändern, und es gibt keine Stabilitäts- oder Supportzusage, baue also noch nichts Tragendes darauf. Die Roadmap steht unter <a href="/de/docs/coming-soon">Demnächst</a>, und jedes Release wird im <a href="/de/changelog">Changelog</a> angekündigt.
</div>

`POST /v2/watch` ist eine API zur Erkennung von Website-Änderungen: Du registrierst eine Seite
einmal, und ein droplet-lokaler Scheduler rendert sie in festem Takt in
echtem Headless Chrome neu, vergleicht jede Capture per Diff mit der vorherigen und
benachrichtigt dich (per HMAC-signiertem Webhook, E-Mail oder beidem), wenn sich die Seite
tatsächlich ändert. Es wird den Cron-Job plus Diffing plus Alerting-
Unterbau ersetzen, den du sonst rund um
[den perceive-Endpunkt](/de/docs/endpoints/perceive.md) aufbauen müsstest: ein Datensatz statt
Scheduler, Storage-Bucket und Vergleichsskript.

Hier ist der kleinste sinnvolle Aufruf. Sende eine URL und erhalte einen Watcher
zurück, der stündlich geplant ist:

```bash
curl -X POST https://api.enconvert.com/v2/watch \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/pricing"
  }'
```

Die Antwort ist der vollständige Watcher-Datensatz. Er ist sofort `active`, und
`next_check_at` wird auf den nächsten Tick des Pollers gesetzt:

```json
{
    "watcher_id": "wat_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7",
    "url": "https://example.com/pricing",
    "status": "active",
    "frequency_minutes": 60,
    "diff_mode": "auto",
    "track_fields": null,
    "webhook_url": null,
    "notify_email": true,
    "consecutive_errors": 0,
    "checks_count": 0,
    "last_check_at": null,
    "next_check_at": "2026-06-24T18:31:07Z",
    "last_change_at": null,
    "created_at": "2026-06-24T18:31:07Z",
    "updated_at": null
}
```

---

## Endpoints

| Methode | Pfad | Zweck |
|--------|------|---------|
| `POST` | `/v2/watch` | Erstellt einen Watcher für eine URL. Gibt `201` zurück. |
| `GET` | `/v2/watch` | Listet die Watcher dieses Projekts auf, neueste zuerst. |
| `GET` | `/v2/watch/{watcher_id}` | Ruft den vollständigen Datensatz eines Watchers ab. |
| `GET` | `/v2/watch/{watcher_id}/snapshots` | Listet den Prüfverlauf eines Watchers auf, neueste zuerst. |
| `PATCH` | `/v2/watch/{watcher_id}` | Aktualisiert Takt, Diff-Einstellungen oder pausiert/setzt fort. |
| `DELETE` | `/v2/watch/{watcher_id}` | Löscht einen Watcher weich (idempotent). |

**Content-Type:** `application/json` bei `POST` und `PATCH`.

---

## Authentifizierung

Authentifiziere dich mit einem privaten Schlüssel im `X-API-Key`-Header für
Server-zu-Server-Aufrufe. Diesen Weg nutzt jedes Beispiel unten.

```http
X-API-Key: sk_your_private_key
```

Öffentliche Schlüssel mit einem JWT-Bearer-Token funktionieren ebenfalls, nach demselben Ablauf wie
bei jedem anderen Endpunkt: Erzeuge ein Token mit deinem `pk_`-Schlüssel und sende es dann
als `Authorization: Bearer <token>`. Der vollständige Ablauf, einschließlich Domain-Sperre
und Token-Refresh, steht im [Authentifizierungs-Leitfaden](/de/docs/authentication.md).

Jeder API-Schlüssel hat eine Allowlist erlaubter Endpunkte. Steht `/v2/watch` nicht
auf der Liste des Schlüssels, wird die Erstellungsanfrage mit `403` abgelehnt. Sobald ein Schlüssel
Watcher erstellen darf, erreicht er auch die zugehörigen Pro-Watcher-Routen:
`GET`, `PATCH` und `DELETE` auf `/v2/watch/{watcher_id}` sowie dessen
`/snapshots`-Seite werden automatisch durchgelassen, sodass du nicht jedes Verb
einzeln freigeben musst.

---

## Wie watch funktioniert

Dahinter steckt keine externe Queue: kein Google Cloud Tasks, kein
Drittanbieter-Scheduler. Der Zeitplan liegt vollständig in der Datenbankspalte
`next_check_at`, und ein prozessinterner Poller steuert ihn:

1. **Erstellen.** Du sendest per `POST` eine URL. Die URL wird auf SSRF geprüft (ein privater,
   Loopback- oder Metadata-Host wird abgelehnt, bevor eine Zeile geschrieben wird), eine
   `wat_`-ID wird erzeugt, und der Watcher wird `active` gespeichert, wobei
   `next_check_at` auf jetzt gesetzt wird.
2. **Beanspruchen.** Ein droplet-lokaler `watch_worker` durchsucht alle 60 Sekunden
   aktive Zeilen, deren `next_check_at` überschritten ist. Er beansprucht sie unter
   `FOR UPDATE SKIP LOCKED` und rückt den Zeitplan jeder einzelnen um ein volles
   Intervall in derselben Transaktion vor, sodass ein langsames Rendering nie
   doppelt beansprucht wird und ein Absturz mitten im Rendering lediglich einen Zyklus überspringt.
3. **Rendern.** Jeder beanspruchte Watcher wird einmal über das gemeinsame
   Headless-Chrome-Singleton gerendert, dieselbe Capture-Pipeline, die auch hinter
   [dem perceive-Endpunkt](/de/docs/endpoints/perceive.md) steht. Das Rendering ist
   credential-frei: Es werden keine Auth-Daten, Cookies oder Header gespeichert, sodass für die
   wiederkehrende Prüfung nichts Geheimes ruhend gespeichert wird.
4. **Bewerten und Diffen.** Ein Rendering, das unter der Qualitätsschwelle (0.4) liegt
   oder als blockiert markiert ist, wird als reine Audit-Prüfung erfasst, ohne Content-Hash,
   sodass es nie zur Diff-Baseline wird und nie eine Benachrichtigung auslöst. Ein
   gutes Rendering wird in eine Capture umgewandelt (Haupttext plus extrahierte
   Struktur), gegen die letzte gute Capture per Diff verglichen, und das Ergebnis wird
   in eine Snapshot-Zeile geschrieben.
5. **Benachrichtigen.** Meldet der Diff eine Änderung, lösen der HMAC-signierte
   Webhook (falls gesetzt) und die E-Mail an den Owner (falls `notify_email` aktiv ist) gleichzeitig
   aus, nach bestem Bemühen (best-effort).
6. **Neu planen.** Der Worker schreibt das nächste `next_check_at`. Drei
   aufeinanderfolgende Render-Fehlschläge pausieren den Watcher und benachrichtigen den Owner
   per E-Mail, statt neu zu planen.

Da der Zeitplan eine Datenbankspalte ist, braucht Downtime keine
Recovery-Logik: Der erste Tick nach dem Start räumt alles Überfällige auf.

---

## Anfrageparameter

### Erstellen (`POST /v2/watch`)

| Parameter | Typ | Standard | Beschreibung |
|-----------|------|---------|-------------|
| `url` | `string` | -- | Die zu überwachende Seite. Muss mit `http://` oder `https://` beginnen. Max. 2,048 Zeichen. Erforderlich. |
| `frequency_minutes` | `integer` | `60` | Minuten zwischen den Prüfungen. Harte Stundenuntergrenze: Minimum `60`, Maximum `43200` (30 Tage). |
| `diff_mode` | `string` | `"auto"` | Welche Diff-Strategie angewendet wird. `auto`, `text`, `structured`, `tables` oder `metadata`. Siehe [Diff-Modi](#diff-modes). |
| `track_fields` | `object` | `null` | Optionale Teilmenge von Feldern/Selektoren, um einzugrenzen, was als Änderung zählt. Siehe [Eine Teilmenge von Feldern verfolgen](#tracking-a-subset-of-fields). |
| `webhook_url` | `string` | `null` | Optionales Ziel für Änderungsbenachrichtigungen. HMAC-signiert, unmittelbar vor jeder Zustellung auf SSRF geprüft. Max. 2,048 Zeichen; muss `http(s)` sein. |
| `notify_email` | `boolean` | `true` | Benachrichtigt den Projekt-Owner per E-Mail bei einer erkannten Änderung und bei Auto-Pause. |

Das Anfrage-Schema ist strikt (`extra="forbid"`): Ein unbekanntes Feld wird
mit `422` abgelehnt. Es gibt hier bewusst keine `auth`-, `cookies`- oder
`headers`-Oberfläche, denn Watcher bleiben credential-frei, dieselbe Haltung wie
[der ingest-Endpunkt](/de/docs/concepts/v1-and-v2.md).

### Aktualisieren (`PATCH /v2/watch/{watcher_id}`)

Jedes Feld ist optional; nur die im Body vorhandenen Schlüssel werden angewendet.

| Parameter | Typ | Beschreibung |
|-----------|------|-------------|
| `frequency_minutes` | `integer` | Neuer Takt. Dieselben Grenzen von `60` bis `43200` wie beim Erstellen. |
| `diff_mode` | `string` | Wechselt die Diff-Strategie. |
| `track_fields` | `object` | Ersetzt die Teilmenge der verfolgten Felder. |
| `webhook_url` | `string` | Setzt einen neuen Webhook. Ein **leerer String** ist das explizite Signal zum Löschen und speichert `NULL`. |
| `notify_email` | `boolean` | Schaltet die Owner-E-Mail um. |
| `status` | `string` | `active` oder `paused`. Fortsetzen aktiviert den Zeitplan neu (`next_check_at` wird auf den nächsten Tick gesetzt); Pausieren leert ihn, sodass der Poller die Zeile nicht mehr beansprucht. |

Ein leerer Body (`{}`) wird mit `422` abgelehnt, statt stillschweigend zu einem No-op zu werden.
Beachte, dass `status` hier nur `active` oder `paused` akzeptiert. Der finale
`deleted`-Zustand wird ausschließlich über `DELETE` erreicht, nie über `PATCH`. Das Fortsetzen eines
pausierten Watchers zählt wie das Hinzufügen eines aktiven Monitors und prüft daher erneut gegen dieselbe
`max_watchers`-Grenze wie beim Erstellen und kann `402` zurückgeben.

---

## Diff-Modi {: #diff-modes }

`diff_mode` wählt aus, welche von vier inhaltstyp-bewussten Strategien die Engine
ausführt. `auto` führt alle vier aus und führt ihre Ergebnisse zusammen; die benannten Modi
beschränken den Diff auf eine einzelne Strategie.

| `diff_mode` | Strategie | Was erkannt wird |
|-------------|----------|---------------|
| `auto` (Standard) | Alle vier unten | Jede Art von Änderung in einem Durchgang. |
| `text` | `SequenceMatcher`-Ratio des Haupttexts | Textkörper hat sich geändert; wird markiert, wenn die Ähnlichkeit unter `0.98` fällt, sodass ein umgestelltes Wort oder eine Whitespace-Änderung den Watcher nicht flattern lässt. Enthält einen Unified Diff, begrenzt auf 100 Zeilen. |
| `structured` | Keyed-List-Matching | Hinzugefügte / entfernte / pro Feld geänderte Einträge bei Links (abgeglichen über `href`) und JSON-LD-Blöcken (abgeglichen über `@type` + `name`). Reihenfolge spielt keine Rolle. |
| `tables` | Kontext-Überschriften-Matching | Tabellen werden über Beschriftung/Überschrift abgeglichen; meldet Änderungen der Zeilenanzahl, hinzugefügte/entfernte Tabellen und Inhaltsänderungen bei gleicher Zeilenanzahl (begrenzt auf 100 Zeilen Kontext). |
| `metadata` | Schlüssel-für-Schlüssel-Dict-Vergleich | Hinzugefügte, entfernte und geänderte Seiten-Metadatenfelder. |

Unabhängig vom gewählten Modus ist das `similarity` des Snapshots immer die
gesamte Ratio der kompletten Capture (0.0 bis 1.0). Bei einem eingeschränkten Modus kann das bedeuten,
dass `similarity` niedrig ausfällt, während `has_changes` `false` ist, weil ein Abschnitt, den du
nicht diffst, sich verändert hat, während sich in deiner gewählten Strategie nichts geändert hat.

### Eine Teilmenge von Feldern verfolgen {: #tracking-a-subset-of-fields }

`track_fields` beschränkt den Diff auf Änderungen, deren Abschnitt, Feld oder Schlüssel
mit einem verfolgten Begriff übereinstimmt. Es akzeptiert ein Objekt, dessen Schlüssel sowie alle
Listenwerte zu den verfolgten Begriffen werden. So verfolgt `{"metadata": ["title"]}` sowohl
den Abschnitt `metadata` als auch das Feld `title`. Der Abgleich erfolgt Token-genau, nicht als
Teilstring: Ein Begriff `price` trifft auf `offers.price`, aber nicht auf
`priceCurrency`.

---

## Antwort

`POST`, `GET /v2/watch/{watcher_id}`, `PATCH` und `DELETE` geben alle
dasselbe Watcher-Objekt zurück.

| Feld | Typ | Beschreibung |
|-------|------|-------------|
| `watcher_id` | `string` | Opake ID (`wat_...`). Verwende sie auf den Pro-Watcher-Routen. |
| `url` | `string` | Die überwachte URL. |
| `status` | `string` | `active`, `paused` oder `deleted`. |
| `frequency_minutes` | `integer` | Aktueller Takt, nach Anwendung der Stundenuntergrenze. |
| `diff_mode` | `string` | Die aktive Diff-Strategie. |
| `track_fields` | `object` | Die Teilmenge der verfolgten Felder, oder `null`. |
| `webhook_url` | `string` | Der Änderungs-Webhook, oder `null`. |
| `notify_email` | `boolean` | Ob die Owner-E-Mail aktiv ist. |
| `consecutive_errors` | `integer` | Aufeinanderfolgende Render-Fehlschläge. Wird bei einer erfolgreichen Prüfung auf `0` zurückgesetzt; bei `3` pausiert der Watcher automatisch. |
| `checks_count` | `integer` | Gesamtzahl durchgeführter Prüfungen, erfolgreich oder fehlgeschlagen. |
| `last_check_at` | `string` | UTC-Zeitstempel der letzten Prüfung, oder `null`. |
| `next_check_at` | `string` | UTC-Zeitstempel der nächsten geplanten Prüfung. `null` bei pausiert oder gelöscht. |
| `last_change_at` | `string` | UTC-Zeitstempel der zuletzt erkannten Änderung, oder `null`. |
| `created_at` | `string` | Wann der Watcher erstellt wurde. |
| `updated_at` | `string` | Letzte Änderung, oder `null`, falls nie aktualisiert. |

`GET /v2/watch` gibt pro Zeile ein kompaktes `WatcherSummary` zurück (es lässt
`diff_mode`, `track_fields`, `webhook_url`, `notify_email` und
`updated_at` weg), eingebettet in eine Page-Envelope:

| Feld | Typ | Beschreibung |
|-------|------|-------------|
| `watchers` | `array` | Die Seite der Summaries, neueste zuerst. |
| `skip` | `integer` | Der von dir angeforderte Offset. |
| `limit` | `integer` | Die aktuell geltende Seitengröße. |
| `has_more` | `boolean` | `true`, wenn über diese Seite hinaus weitere Watcher existieren. |

### Snapshot-Verlauf

`GET /v2/watch/{watcher_id}/snapshots` gibt die Prüf-Timeline des Watchers zurück,
neueste zuerst. Jeder Eintrag enthält das Diff-Ergebnis. Der eigentliche
Snapshot-Capture-Inhalt liegt im Storage und wird hier nicht zurückgegeben.

| Feld | Typ | Beschreibung |
|-------|------|-------------|
| `checked_at` | `string` | UTC-Zeitstempel der Prüfung. |
| `has_changes` | `boolean` | Ob diese Prüfung eine Änderung erkannt hat. |
| `similarity` | `number` | Gesamte Ratio der kompletten Capture, 0.0 bis 1.0, oder `null`. |
| `render_quality` | `number` | Render-Qualitätswert für die Prüfung, oder `null`. |
| `change_count` | `integer` | Anzahl der strukturierten Änderungsdatensätze. |
| `changes` | `array` | Der strukturierte Diff: ein Datensatz pro Änderung, jeweils mit `section`, `kind` (`added`/`removed`/`modified`), `key`, `field`, `before`, `after`. |

> **Wichtiger Hinweis.** Die Werte `before` und `after` innerhalb von `changes` sind
> roher Seiteninhalt (Linktext, Metadaten, JSON-LD-Werte), keine bereinigte
> Ausgabe. Wenn du sie in HTML renderst (ein Dashboard, eine E-Mail), musst du
> sie selbst escapen. Lange String-Werte sind bereits auf 2,000
> Zeichen gekürzt, und ein einzelner Diff ist auf 500 Änderungsdatensätze begrenzt.

---

## Lebenszyklus und Zeitplanung

Ein Watcher durchläuft drei Zustände:

- **`active`:** auf dem Zeitplan des Pollers. `next_check_at` ist gesetzt.
- **`paused`:** nicht mehr im Zeitplan (`next_check_at` ist `null`). Wird
  entweder über `PATCH {"status": "paused"}` erreicht oder automatisch nach drei
  aufeinanderfolgenden Render-Fehlschlägen.
- **`deleted`:** finaler Tombstone, nur über `DELETE` erreichbar. Die Zeile bleibt
  erhalten (sodass der Prüfverlauf erhalten bleibt), erscheint aber nie in Listen und liefert
  `404` auf den Pro-Watcher-Routen.

Die Stundenuntergrenze wird an zwei Stellen durchgesetzt: beim Erstellen/Aktualisieren
und erneut vom Scheduler, wenn er `next_check_at` vorrückt. So kann selbst eine Zeile, deren
Takt direkt in der Datenbank bearbeitet wurde, die Untergrenze nie unterschreiten.

### Auto-Pause

Nach **drei aufeinanderfolgenden Render-Fehlschlägen** wird der Watcher auf `paused` gesetzt,
sein Zeitplan wird geleert, und falls `notify_email` aktiv ist, erhält der Owner eine
E-Mail zum pausierten Watcher. Eine einzelne erfolgreiche Prüfung setzt `consecutive_errors`
auf `0` zurück. Um einen pausierten Watcher neu zu starten, setze ihn per `PATCH` zurück auf `active`,
wodurch `next_check_at` für den nächsten Tick neu aktiviert wird.

### Löschen

`DELETE /v2/watch/{watcher_id}` ist ein Soft-Delete: Es setzt `status` auf
`deleted`, leert `next_check_at` und gibt den tombstoned Datensatz mit
`200` zurück. Es ist idempotent: Das Löschen eines bereits gelöschten Watchers gibt
denselben Datensatz unverändert zurück. Gelöschte Watcher zählen sofort nicht mehr gegen dein
`max_watchers`-Kontingent (nur `active`-Watcher zählen).

---

## Codebeispiele

### curl: Erstellen mit Standardwerten

```bash
curl -X POST https://api.enconvert.com/v2/watch \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/pricing"
  }'
```

### curl: alle 6 Stunden, Webhook plus Tabellen-Tracking

```bash
curl -X POST https://api.enconvert.com/v2/watch \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/pricing",
    "frequency_minutes": 360,
    "diff_mode": "tables",
    "webhook_url": "https://hooks.example.com/enconvert",
    "notify_email": false
  }'
```

### curl: Pausieren, dann fortsetzen

```bash
curl -X PATCH \
  https://api.enconvert.com/v2/watch/wat_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7 \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{"status": "paused"}'

curl -X PATCH \
  https://api.enconvert.com/v2/watch/wat_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7 \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{"status": "active"}'
```

### Python

```python
import requests

BASE = "https://api.enconvert.com"
HEADERS = {"X-API-Key": "sk_your_private_key"}

# Create a watcher.
created = requests.post(
    f"{BASE}/v2/watch",
    headers=HEADERS,
    json={
        "url": "https://example.com/pricing",
        "frequency_minutes": 120,
        "diff_mode": "auto",
    },
)
created.raise_for_status()
watcher = created.json()
watcher_id = watcher["watcher_id"]

# Later: pull the check history and read the diff verdicts.
snaps = requests.get(
    f"{BASE}/v2/watch/{watcher_id}/snapshots",
    headers=HEADERS,
)
snaps.raise_for_status()
for snap in snaps.json()["snapshots"]:
    if snap["has_changes"]:
        print(snap["checked_at"], snap["change_count"], snap["similarity"])
```

### Node.js

```javascript
const BASE = "https://api.enconvert.com";
const HEADERS = {
    "Content-Type": "application/json",
    "X-API-Key": "sk_your_private_key"
};

// Create a watcher.
const created = await fetch(`${BASE}/v2/watch`, {
    method: "POST",
    headers: HEADERS,
    body: JSON.stringify({
        url: "https://example.com/pricing",
        frequency_minutes: 120,
        diff_mode: "auto"
    })
});
const watcher = await created.json();

// List this project's watchers, newest first.
const list = await fetch(`${BASE}/v2/watch?limit=20`, {
    headers: { "X-API-Key": "sk_your_private_key" }
}).then(r => r.json());

console.log(watcher.watcher_id, list.has_more);
```

---

## Fehlerantworten

| Status | Bedingung |
|--------|-----------|
| `400 Bad Request` | Die URL löst zu einer privaten, Loopback-, Link-Local- oder Metadata-Adresse auf (SSRF-Schutz beim Erstellen). |
| `401 Unauthorized` | Fehlender oder ungültiger API-Schlüssel / JWT-Token. |
| `402 Payment Required` | Watch ist auf deinem Plan nicht aktiviert, oder deine Anzahl aktiver Watcher hat `max_watchers` erreicht (wird auch beim Fortsetzen von pausiert→aktiv durchgesetzt). |
| `403 Forbidden` | `/v2/watch` gehört nicht zu den erlaubten Endpunkten des API-Schlüssels. |
| `404 Not Found` | Unbekannte `watcher_id`, eine, die einem anderen Projekt gehört, oder eine bereits soft-gelöschte. |
| `422 Unprocessable Entity` | `frequency_minutes` unter `60` oder über `43200`, ein leerer `PATCH`-Body (`{}`), ein ungültiger Enum-Wert in `diff_mode` oder `status`, eine `url`/`webhook_url`, die nicht `http(s)` ist, oder ein beliebiges unbekanntes Feld. |
| `500 Internal Server Error` | Der Watcher konnte nicht erstellt werden. Erneut versuchen. |

Die vollständige Statuscode-Referenz findest du im [Fehlercode-Leitfaden](/de/docs/reference/errors.md).

---

## Limits

| Limit | Wert |
|-------|-------|
| URL-Länge | 2,048 Zeichen |
| Aktive Watcher (`max_watchers`) | 20 / 100 / 500 auf Indie / Studio / Production; Watch ist im Founding-Plan nicht verfügbar |
| Ops pro Prüfung | 0 (Watcher schöpfen nie aus dem monatlichen Ops-Kontingent) |
| `webhook_url`-Länge | 2,048 Zeichen |
| `frequency_minutes` | 60 bis 43,200 (1 Stunde bis 30 Tage) |
| Stundenuntergrenze | 60 Minuten, durchgesetzt beim Erstellen, Aktualisieren und bei der Zeitplanung |
| Poll-Intervall | 60 Sekunden (eine Prüfung löst innerhalb einer Minute nach ihrer geplanten Zeit aus) |
| Aufeinanderfolgende Fehler vor Auto-Pause | 3 |
| Render-Qualitätsuntergrenze (kein Diff darunter) | 0.4 |
| Ähnlichkeitsschwelle für Textänderungen | 0.98 |
| Unified-Text-Diff | begrenzt auf 100 Zeilen |
| Änderungsdatensätze pro Diff | begrenzt auf 500 |
| String-Wert pro Änderung | gekürzt auf 2,000 Zeichen |
| Erfasster, per Diff verglichener Textkörper | begrenzt auf 200,000 Zeichen |
| Seitengröße Liste/Snapshot | Standard 20, max. 100 |

---

## Häufig gestellte Fragen

### Wie richte ich Webhooks für die Überwachung von Website-Änderungen ein?

Übergib `webhook_url` beim Erstellen des Watchers mit `POST /v2/watch`, oder füge es später über `PATCH /v2/watch/{watcher_id}` hinzu. Wenn eine Prüfung eine Änderung erkennt, sendet EnConvert einen HMAC-signierten POST an diese URL; das Ziel wird unmittelbar vor jeder Zustellung auf SSRF geprüft, und ein leerer String bei `PATCH` löscht ihn.

### Wie oft kann die API eine Seite auf Änderungen prüfen?

`frequency_minutes` legt den Takt fest, von `60` (die harte Stundenuntergrenze) bis `43200` (30 Tage). Der Poller durchsucht alle 60 Sekunden, sodass eine Prüfung innerhalb einer Minute nach ihrer geplanten Zeit auslöst.

### Warum hat sich mein Watcher selbst pausiert?

Drei aufeinanderfolgende Render-Fehlschläge pausieren einen Watcher automatisch, leeren seinen Zeitplan und benachrichtigen den Owner per E-Mail, falls `notify_email` aktiv ist. Setze ihn per `PATCH` mit `{"status": "active"}` zurück, um `next_check_at` neu zu aktivieren; eine einzelne erfolgreiche Prüfung setzt `consecutive_errors` auf `0` zurück.

### Kann ich nur einen Teil einer Seite überwachen, z. B. einen Preis oder eine Tabelle?

Ja. `track_fields` beschränkt den Diff auf Änderungen, deren Abschnitt, Feld oder Schlüssel mit einem verfolgten Begriff übereinstimmt (Token-genauer Abgleich, sodass `price` auf `offers.price`, aber nicht auf `priceCurrency` trifft), und `diff_mode` kann die Erkennung auf eine einzelne Strategie wie `tables`, `structured`, `text` oder `metadata` beschränken.
