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

# AI Rendering: Kopien Ihrer Seiten, die KI-Agenten lesen können

> querying.ai rendert jede veröffentlichte Seite so, wie ein Mensch sie sieht, und schreibt den Text ihrer Bilder in deren Alt-Text. Ihr CDN bewahrt diese Kopien auf und sendet sie an ChatGPT und andere KI-Agenten, während Menschen weiter Ihre Website erhalten.

KI-Agenten lesen das HTML, das Ihr Server sendet. Sie führen keine Skripte aus, daher erreichen sie Preise, Bewertungen
und Daten, die ein Skript zeichnet, nie, und den Text in einem Bild lesen sie nur aus seinem Alt-Text. AI Rendering
rendert jede veröffentlichte Seite so, wie ein Mensch sie sieht, entfernt die Skripte und schreibt den Text jedes Bildes
in seinen Alt-Text. Ihr CDN lädt diese Kopien herunter und antwortet KI-Agenten damit. Menschen, Suchmaschinen und alle
anderen Anfragen erreichen Ihre Website wie bisher, und querying.ai liegt nie auf dem Weg einer Anfrage an Ihre Website.

[GEO Score](/de/research/geo-score.md) misst die Lücke: `rendering.score` ist der Anteil einer Seite, den eine Kopie enthält.
Sobald Ihr CDN die Kopie sendet, lesen die Agenten, die sie erhalten, so viel.

## So funktioniert es

1. **Website verbinden.** Sie erhalten einen Site-Key einmal. Er kann nur die Kopien Ihrer Website lesen.
2. **Seiten veröffentlichen.** Senden Sie eine Seiten-URL. querying.ai rendert sie aus dem Land Ihrer Website, bewahrt die
   Kopie auf und rendert die Seite alle `refreshDays` Tage neu (standardmäßig 28).
3. **Kopien synchronisieren.** Alle 10 Minuten liest Ihr CDN mit dem Site-Key die Kopienliste, lädt neue Kopien herunter und
   entfernt die nicht mehr gelisteten.
4. **KI-Agenten antworten.** Sendet ein KI-Agent ein GET, antwortet Ihr CDN mit der gespeicherten Kopie. Eine Seite ohne
   Kopie, eine abgelaufene Kopie und jeder Fehler gehen an Ihren Ursprungsserver.

## Website verbinden

```bash theme={"theme":"css-variables"}
curl -X POST https://api.querying.ai/v1/ai-rendering/sites \
  -H "Authorization: Bearer $QUERYING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "host": "www.example.com" }'
```

```json theme={"theme":"css-variables"}
{
  "success": true,
  "site": {
    "id": "5b0e9f52-6a3c-4f0e-8d0b-2c7e4a1f9d33",
    "host": "www.example.com",
    "refreshDays": 28,
    "country": "US",
    "keyPrefix": "rs_8f2c4a1b",
    "pages": 0,
    "createdAt": "2026-10-23T01:00:00.000Z",
    "updatedAt": "2026-10-23T01:00:00.000Z"
  },
  "key": "rs_8f2c4a1b…"
}
```

Bewahren Sie den `key` auf: Nur diese Antwort enthält ihn, und Ihr CDN sendet ihn. `POST /v1/ai-rendering/sites/{id}/key`
gibt einen neuen aus und stoppt den alten sofort.

| Feld | Pflicht | Beschreibung |
| - | - | - |
| `host` | Ja | Der öffentliche Hostname, den Ihr CDN ausliefert, etwa `www.example.com`. Jeden Host einmal verbinden, bis zu 20 pro Konto. |
| `refreshDays` | Nein | Tage zwischen zwei Renderings einer veröffentlichten Seite, `1`–`28`, Standard `28`. Die Datei einer Kopie läuft nach 30 Tagen ab. |
| `country` | Nein | Land, aus dem Seiten gerendert werden: `US` (Standard) oder `KR`, wie das Leserland von GEO Score. Wählen Sie das Land, aus dem Ihre Kunden surfen. |

Später ändern Sie sie mit `PATCH /v1/ai-rendering/sites/{id}`.

## Seite veröffentlichen

```bash theme={"theme":"css-variables"}
curl -X POST https://api.querying.ai/v1/ai-rendering/sites/5b0e9f52-6a3c-4f0e-8d0b-2c7e4a1f9d33/pages \
  -H "Authorization: Bearer $QUERYING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://www.example.com/products/sunscreen" }'
```

Die Seite wird gerendert:

```json theme={"theme":"css-variables"}
{
  "success": true,
  "page": {
    "id": "a3d1c9e0-7b52-4e1f-9c84-0f6b2d5e7a10",
    "url": "https://www.example.com/products/sunscreen",
    "path": "/products/sunscreen",
    "status": "rendering",
    "copy": null,
    "taskId": null,
    "renderedAt": null,
    "expiresAt": null,
    "publishedAt": "2026-10-23T01:00:00.000Z",
    "refreshAt": "2026-10-23T07:00:00.000Z",
    "refreshTaskId": "7c4e2a90-1d3b-4f6a-8e25-9b0c1f3d4e57"
  },
  "taskId": "7c4e2a90-1d3b-4f6a-8e25-9b0c1f3d4e57"
}
```

Ein Rendering dauert etwa 30 Sekunden. Verfolgen Sie es mit `GET /v1/async/task/{taskId}` oder lesen Sie den `status` der
Seite aus `GET /v1/ai-rendering/sites/{id}`:

* `ready`: Die Kopie steht in Ihrer Kopienliste. `copy` ist ihre HTML-Datei.
* `rendering`: Die erste Kopie wird erstellt.
* `failed`: Das erste Rendering ist fehlgeschlagen. Die Seite wird um `refreshAt` erneut gerendert.
* `expired`: Die Kopie ist abgelaufen und bis zum nächsten Rendering nicht in der Liste.

Eine Website fasst bis zu 1.000 Seiten. Eine veröffentlichte Seite erneut zu senden, rendert sie sofort. Eine Seite ist ihr
Pfad samt Query, ohne Tracking-Parameter wie `utm_*` und `gclid`. `DELETE /v1/ai-rendering/sites/{id}/pages/{pageId}` nimmt
eine Seite herunter.

Um die Kopie eines [GEO Score](/de/research/geo-score.md) ohne neues Rendering zu veröffentlichen, senden Sie seine Task-ID:
`{ "taskId": "0b6c1f7e-…" }`. Es muss ein abgeschlossener GEO Score Ihres Kontos sein, gemessen aus dem Land der Website.

## CDN einrichten

Speichern Sie dies als `ai-rendering.mjs` neben Ihrem CDN-Code. `syncCopies` gleicht einen Speicher mit Ihrer Kopienliste ab,
und `copyFor` beantwortet das GET eines KI-Agenten daraus:

```js theme={"theme":"css-variables"}
export const COPIES_URL = "https://api.querying.ai/v1/ai-rendering/copies";

export const AGENTS = [
	"chatgpt-user",
	"codex",
	"claude-user",
	"claude-code",
	"perplexity-user",
	"gemini-deep-research",
	"duckassistbot",
	"amzn-user",
	"mistralai-user",
	"oai-searchbot",
];

export const DOWNLOADS_PER_SYNC = 40;

const EXPIRY_MARGIN_MS = 10 * 60_000;

const TRACKING_PARAM =
	/^(?:utm_[a-z0-9_]+|gclid|gbraid|wbraid|dclid|fbclid|msclkid|twclid|ttclid|srsltid|mc_cid|mc_eid|_ga|_gl)$/i;

export function pagePath(url) {
	const kept = [...url.searchParams].filter(([name]) => !TRACKING_PARAM.test(name));
	return (url.pathname || "/") + (kept.length ? "?" + new URLSearchParams(kept).toString() : "");
}

export async function copyKey(host, path) {
	const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(host.toLowerCase() + path));
	return "copy:" + [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
}

export function agentRequest(request, agents = AGENTS) {
	if (request.method !== "GET") return false;
	const userAgent = (request.headers.get("user-agent") ?? "").toLowerCase();
	return agents.some((token) => userAgent.includes(token));
}

export async function syncCopies(options) {
	const {
		siteKey,
		store,
		copiesUrl = COPIES_URL,
		downloads = DOWNLOADS_PER_SYNC,
		fetch: send = fetch,
		now = Date.now,
	} = options;
	if (!siteKey) throw new Error("AI Rendering: no site key");
	const answer = await send(copiesUrl, { headers: { "X-Querying-Site-Key": siteKey } });
	if (!answer.ok) throw new Error(`AI Rendering: the copy list answered ${answer.status}`);
	const { host, copies } = await answer.json();
	const stored = await store.list();
	const listed = new Set();
	const result = { listed: copies.length, downloaded: 0, failed: 0, waiting: 0, removed: 0 };
	for (const copy of copies) {
		const key = await copyKey(host, copy.path);
		listed.add(key);
		if (stored.get(key) === copy.renderedAt || Date.parse(copy.expiresAt) - now() < EXPIRY_MARGIN_MS) continue;
		if (result.downloaded + result.failed >= downloads) {
			result.waiting++;
			continue;
		}
		try {
			const file = await send(copy.copy);
			if (!file.ok) throw new Error(String(file.status));
			await store.put(key, await file.arrayBuffer(), { renderedAt: copy.renderedAt, expiresAt: copy.expiresAt });
			result.downloaded++;
		} catch {
			result.failed++;
		}
	}
	for (const key of stored.keys())
		if (!listed.has(key)) {
			await store.delete(key);
			result.removed++;
		}
	return result;
}

export async function copyFor(request, store, agents = AGENTS) {
	if (!agentRequest(request, agents)) return null;
	try {
		const url = new URL(request.url);
		const copy = await store.get(await copyKey(url.hostname, pagePath(url)));
		if (!copy || !(Date.parse(copy.expiresAt) > Date.now())) return null;
		return new Response(copy.body, {
			status: 200,
			headers: {
				"Content-Type": "text/html; charset=utf-8",
				"Cache-Control": "private, no-store",
				Vary: "User-Agent",
				"X-Querying-Rendering": "HIT",
				"X-Querying-Rendered-At": copy.renderedAt,
			},
		});
	} catch {
		return null;
	}
}
```

**Cloudflare Worker**

Der Worker legt Kopien in Workers KV ab und synchronisiert sie alle 10 Minuten per Cron-Trigger. Jede Kopie läuft in KV
zusammen mit ihrer Datei ab.

```bash theme={"theme":"css-variables"}
npx wrangler kv namespace create AI_RENDERING
npx wrangler secret put QUERYING_SITE_KEY
```

`wrangler.toml`:

```toml theme={"theme":"css-variables"}
name = "ai-rendering"
main = "cloudflare-worker.mjs"
compatibility_date = "2026-10-01"
routes = [{ pattern = "www.example.com/*", zone_name = "example.com" }]
kv_namespaces = [{ binding = "AI_RENDERING", id = "<namespace id>" }]

[triggers]
crons = ["*/10 * * * *"]
```

`cloudflare-worker.mjs`:

```js theme={"theme":"css-variables"}
import { copyFor, syncCopies } from "./ai-rendering.mjs";

export function kvStore(kv) {
	return {
		async get(key) {
			const { value, metadata } = await kv.getWithMetadata(key, "arrayBuffer");
			return value && metadata ? { body: value, ...metadata } : null;
		},
		async list() {
			const stored = new Map();
			let cursor;
			do {
				const page = await kv.list({ prefix: "copy:", cursor });
				for (const key of page.keys) stored.set(key.name, key.metadata?.renderedAt);
				cursor = page.list_complete ? undefined : page.cursor;
			} while (cursor);
			return stored;
		},
		put: (key, body, meta) =>
			kv.put(key, body, { expiration: Math.floor(Date.parse(meta.expiresAt) / 1000), metadata: meta }),
		delete: (key) => kv.delete(key),
	};
}

export default {
	async fetch(request, env) {
		return (await copyFor(request, kvStore(env.AI_RENDERING))) ?? fetch(request);
	},
	async scheduled(_controller, env, ctx) {
		ctx.waitUntil(syncCopies({ siteKey: env.QUERYING_SITE_KEY, store: kvStore(env.AI_RENDERING) }));
	},
};
```

Im Workers-Free-Tarif erlaubt KV 1.000 Schreibvorgänge pro Tag, daher kann die erste Synchronisierung einer großen
Website länger als einen Tag dauern.

**Andere CDNs und Server**

Verwenden Sie dasselbe Modul mit einem Speicher Ihrer Plattform: ein Objekt mit `get(key)`, `list()`,
`put(key, body, meta)` und `delete(key)`. `list()` gibt jeden gespeicherten Schlüssel mit dem `renderedAt` zurück, mit dem
er gespeichert wurde. Führen Sie `syncCopies` nach Zeitplan und `copyFor` bei jeder Seitenanfrage aus. Cacht Ihr CDN nur
nach URL, nehmen Sie den Agenten in den Cache-Schlüssel auf, damit ein Mensch nie eine Kopie erhält.

Prüfen Sie nach der nächsten Synchronisierung eine veröffentlichte Seite so, wie ChatGPT sie öffnet. Die Kopie trägt
`X-Querying-Rendering: HIT`:

```bash theme={"theme":"css-variables"}
curl -sI -A "Mozilla/5.0 (compatible; ChatGPT-User/1.0; +https://openai.com/bot)" https://www.example.com/products/sunscreen
```

## Die Kopienliste

Ihr CDN liest `GET https://api.querying.ai/v1/ai-rendering/copies` mit dem Site-Key in `X-Querying-Site-Key`:

```json theme={"theme":"css-variables"}
{
  "success": true,
  "host": "www.example.com",
  "copies": [
    {
      "path": "/products/sunscreen",
      "url": "https://www.example.com/products/sunscreen",
      "copy": "https://api.querying.ai/v1/evidence/1792717200-9f3c2a7d1e4b.html",
      "renderedAt": "2026-10-23T01:00:31.000Z",
      "expiresAt": "2026-11-22T01:00:00.000Z"
    }
  ]
}
```

Sie enthält jede veröffentlichte Seite, deren Kopie nicht abgelaufen ist. `copy` lässt sich ohne Schlüssel herunterladen.
`renderedAt` ändert sich, wenn die Seite neu gerendert wird; laden Sie eine Kopie also nur herunter, wenn ihr `renderedAt`
neu ist. Entfernen Sie die gespeicherten Kopien von Seiten, die nicht mehr in der Liste stehen. Liste und Downloads sind
kostenlos.

## Agenten

`AGENTS` im Modul legt fest, wer die Kopie erhält, über ein Token an beliebiger Stelle im User-Agent, ohne Beachtung der
Groß- und Kleinschreibung.

* **Standard:** die Agenten, die ein Mensch beim Fragen an eine Seite schickt (`chatgpt-user`, `codex`, `claude-user`,
  `claude-code`, `perplexity-user`, `gemini-deep-research`, `duckassistbot`, `amzn-user`, `mistralai-user`), und der Crawler
  der ChatGPT-Suche, `oai-searchbot`. Die ChatGPT-Suche antwortet aus der Seite, die ihr Crawler speichert, und genau die misst
  der `score` von GEO Score. Ihr Index ändert sich, nachdem OpenAI die Seite erneut gecrawlt hat.
* **Bei Bedarf hinzufügen:** `claude-searchbot` und `perplexitybot`.
* **Weglassen:** Trainings-Crawler (GPTBot, ClaudeBot) und Websuche-Crawler (Googlebot, Bingbot). Googlebot führt Skripte selbst
  aus.

## Regeln der Suchmaschinen einhalten

Bots andere Inhalte als Menschen zu senden, ist Cloaking. Google wertet eine für Bots gerenderte Seite nicht als Cloaking,
solange der Inhalt ähnlich ist ([Dynamic rendering](https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering)).
OpenAI hat keine Cloaking-Richtlinie veröffentlicht, daher richten Sie die Kopie nach Googles Regel aus:

* Die Kopie ist die Seite, die der Browser eines Menschen gezeichnet hat. querying.ai entfernt Skripte und fügt nur den Text
  in Bildern hinzu, in deren Alt-Text, dem Platz für den Text eines Bildes.
* Sie wird unter der eigenen URL der Seite ausgeliefert.
* Sie bleibt aktuell. querying.ai rendert sie alle `refreshDays` neu, und das Modul sendet keine abgelaufene Kopie.
* Wenn Sie eine Kopie bearbeiten, entfernen oder korrigieren Sie nur: ein falsch gelesenes Zeichen berichtigen, ein Pop-up
  entfernen. Fügen Sie keine Aussagen, Preise oder Links hinzu, die Menschen nicht sehen.

## Was die Kopie ist

Die Kopie ist das HTML, das der Browser eines Menschen gezeichnet hat, nachdem die Seite bis zum Ende gescrollt wurde: der
Text, den Skripte geladen haben, die Seite jedes Frames an ihrer Stelle, alle Skripte entfernt außer strukturierten Daten
(JSON-LD), und der Text jedes großen Bildes in seinem Alt-Text. Links und Bildquellen bleiben absolut, sodass sich die Kopie
wie die Seite liest. Sie zeigt die Seite beim letzten Rendering: Eine Preis- oder Bestandsänderung erreicht Agenten beim
nächsten Rendering, höchstens `refreshDays` später, plus die Zeit bis zur Synchronisierung Ihres CDN. Bildtext wird so
übernommen, wie er gelesen wurde, ein falsch gelesenes Zeichen geht also so hinaus.

## Abrechnung

Ein Rendering kostet 5 Credits, plus 5 für jedes Bild, dessen Text gelesen wird, und 2 für jedes Bild, das Ihr Konto in den
letzten 90 Tagen gelesen hat (dieselbe Datei, auf dieselbe Weise gelesen), höchstens 12 Bilder. Beim Absenden werden 65 Credits
reserviert, beim Abschluss wird abgebucht, was das Rendering verbraucht hat. Ein fehlgeschlagenes Rendering wird nicht berechnet.
Eine Aktualisierung kostet dasselbe und wird dem Konto der Website berechnet; ein Konto ohne Credits wartet eine Stunde, bevor die
Seite erneut versucht wird. Website verbinden, die Kopie eines GEO Score veröffentlichen, die Kopienliste und das Herunterladen
von Kopien sind kostenlos. Ihr CDN sendet die Kopien, daher kostet Agenten-Traffic bei querying.ai nichts. Siehe
[Credits und Limits](/de/concepts/credits-and-limits.md).

## Fehler

| Status | Code | Ursache |
| - | - | - |
| `401` | `UNAUTHORIZED` | Die Kopienliste erhielt keinen Site-Key, einen ersetzten Schlüssel oder den Schlüssel einer entfernten Website. |
| `402` | `INSUFFICIENT_CREDITS` | Das Guthaben deckt die Reservierung von 65 Credits für ein Rendering nicht. |
| `403` | `KEY_SCOPE_DENIED` | Der API-Schlüssel ist auf Engines beschränkt, und `GEO_SCORE` gehört nicht dazu. |
| `404` | `NOT_FOUND` | Keine Website, Seite oder GEO Score Ihres Kontos mit dieser ID. |
| `409` | `SITE_EXISTS`, `SITE_LIMIT`, `PAGE_LIMIT`, `TASK_NOT_COMPLETED` | Der Host ist schon verbunden, das Konto hat 20 Websites, die Website hat 1.000 Seiten, oder der GEO Score ist nicht abgeschlossen. |
| `410` | `COPY_EXPIRED` | Die Kopie des GEO Score ist abgelaufen. Veröffentlichen Sie die Seite per URL. |
| `422` | `VALIDATION_ERROR`, `NO_RENDERED_COPY` | Ein Feld ist ungültig, die URL liegt auf einem anderen Host, der GEO Score wurde aus einem anderen Land gemessen, oder er hat keine Kopie erstellt. |
| `429`, `503` | | Die Plan-Parallelität oder die Render-Warteschlange ist voll. Erneut senden nach `Retry-After`. |

Die Endpunkte stehen in der [API-Referenz](/de/api-reference/ai-rendering/create-site.md).


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