> ## 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: cópias das suas páginas que os agentes de IA conseguem ler

> O querying.ai renderiza cada página que você publica como uma pessoa a vê e escreve o texto das imagens no texto alternativo delas. Sua CDN guarda essas cópias e as envia ao ChatGPT e a outros agentes de IA, enquanto as pessoas continuam recebendo o seu site.

Os agentes de IA leem o HTML que o seu servidor envia. Eles não executam scripts, então preços, avaliações e
especificações desenhados por um script nunca chegam até eles, e o texto dentro de uma imagem só é lido no texto
alternativo dela. O AI Rendering renderiza cada página que você publica como uma pessoa a vê, remove os scripts e escreve o
texto de cada imagem no texto alternativo dela. Sua CDN baixa essas cópias e responde com elas aos agentes de IA. Pessoas,
buscadores e todas as outras solicitações chegam ao seu site como antes, e o querying.ai nunca fica no caminho de uma
solicitação ao seu site.

O [GEO Score](/pt/research/geo-score.md) mede a diferença: `rendering.score` é a parte de uma página que uma cópia contém. Quando
sua CDN envia a cópia, os agentes que a recebem leem essa parte.

## Como funciona

1. **Conecte o seu site.** Você recebe uma chave de site uma única vez. Ela só pode ler as cópias do seu site.
2. **Publique páginas.** Envie a URL de uma página. O querying.ai a renderiza a partir do país do seu site, guarda a cópia e
   renderiza a página de novo a cada `refreshDays` dias (28 por padrão).
3. **Sincronize as cópias.** A cada 10 minutos sua CDN lê a lista de cópias com a chave de site, baixa as cópias novas e
   remove as que não estão mais na lista.
4. **Responda aos agentes de IA.** Quando um agente de IA envia um GET, sua CDN responde com a cópia guardada. Uma página sem
   cópia, uma cópia expirada e qualquer erro vão para o seu servidor de origem.

## Conectar um site

```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…"
}
```

Guarde a `key`: só esta resposta a contém, e sua CDN a envia. `POST /v1/ai-rendering/sites/{id}/key` gera uma nova e
desativa a antiga na hora.

| Campo | Obrigatório | Descrição |
| - | - | - |
| `host` | Sim | O nome de host público servido pela sua CDN, como `www.example.com`. Conecte cada host uma vez, até 20 por conta. |
| `refreshDays` | Não | Dias entre renderizações de uma página publicada, `1`–`28`, padrão `28`. O arquivo de uma cópia expira após 30 dias. |
| `country` | Não | De onde as páginas são renderizadas: `US` (padrão) ou `KR`, como o país do leitor do GEO Score. Escolha o país de onde seus clientes navegam. |

Altere-os depois com `PATCH /v1/ai-rendering/sites/{id}`.

## Publicar uma página

```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" }'
```

A página está sendo renderizada:

```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"
}
```

Uma renderização leva cerca de 30 segundos. Acompanhe com `GET /v1/async/task/{taskId}` ou leia o `status` da página em
`GET /v1/ai-rendering/sites/{id}`:

* `ready`: a cópia está na sua lista de cópias. `copy` é o arquivo HTML dela.
* `rendering`: a primeira cópia está sendo criada.
* `failed`: a primeira renderização falhou. A página é renderizada de novo em `refreshAt`.
* `expired`: a cópia expirou e saiu da lista até a próxima renderização.

Um site comporta até 1.000 páginas. Enviar de novo uma página publicada a renderiza agora. Uma página é o caminho e a
consulta dela, sem parâmetros de rastreamento como `utm_*` e `gclid`. `DELETE /v1/ai-rendering/sites/{id}/pages/{pageId}`
retira uma página.

Para publicar a cópia feita por um [GEO Score](/pt/research/geo-score.md) sem renderizar de novo, envie o id da tarefa:
`{ "taskId": "0b6c1f7e-…" }`. Ele precisa ser um GEO Score concluído da sua conta, medido a partir do país do site.

## Configurar a sua CDN

Salve isto como `ai-rendering.mjs` ao lado do código da sua CDN. `syncCopies` alinha um armazenamento com a sua lista de
cópias, e `copyFor` responde a partir dele ao GET de um agente de IA:

```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**

O Worker guarda as cópias no Workers KV e as sincroniza a cada 10 minutos com um cron trigger. Cada cópia expira no KV
junto com o arquivo dela.

```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) }));
	},
};
```

No plano Workers Free, o KV aceita 1.000 gravações por dia, então a primeira sincronização de um site grande pode levar
mais de um dia.

**Outras CDNs e servidores**

Use o mesmo módulo com um armazenamento da sua plataforma: um objeto com `get(key)`, `list()`, `put(key, body, meta)` e
`delete(key)`. `list()` devolve cada chave guardada com o `renderedAt` com que foi guardada. Execute `syncCopies` de forma
agendada e `copyFor` a cada solicitação de página. Se a sua CDN faz cache só pela URL, inclua o agente na chave de cache
para que uma pessoa nunca receba uma cópia.

Depois da próxima sincronização, confira uma página publicada como o ChatGPT a abriria. A cópia traz
`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
```

## A lista de cópias

Sua CDN lê `GET https://api.querying.ai/v1/ai-rendering/copies` com a chave de site em `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"
    }
  ]
}
```

Ela lista cada página publicada cuja cópia não expirou. `copy` é baixado sem chave. `renderedAt` muda quando a página é
renderizada de novo, então baixe uma cópia só quando o `renderedAt` dela for novo. Remova as cópias guardadas das páginas que
não estão mais na lista. A lista e os downloads são gratuitos.

## Agentes

`AGENTS` no módulo escolhe quem recebe a cópia, por um token em qualquer parte do User-Agent, sem diferenciar maiúsculas.

* **Padrão:** os agentes que uma pessoa envia a uma página ao fazer uma pergunta (`chatgpt-user`, `codex`, `claude-user`,
  `claude-code`, `perplexity-user`, `gemini-deep-research`, `duckassistbot`, `amzn-user`, `mistralai-user`) e o rastreador da
  busca do ChatGPT, `oai-searchbot`. A busca do ChatGPT responde a partir da página que o rastreador guarda, que é o que o
  `score` do GEO Score mede. O índice muda depois que a OpenAI rastreia a página de novo.
* **Adicione se quiser:** `claude-searchbot` e `perplexitybot`.
* **Deixe de fora:** rastreadores de treinamento (GPTBot, ClaudeBot) e de busca na web (Googlebot, Bingbot). O Googlebot
  executa os scripts sozinho.

## Siga as regras dos buscadores

Enviar aos robôs um conteúdo diferente do que as pessoas veem é cloaking. O Google não considera cloaking uma página
renderizada para robôs desde que o conteúdo seja semelhante
([Dynamic rendering](https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering)). A OpenAI não
publicou nenhuma política de cloaking, então mantenha a cópia dentro da regra do Google:

* A cópia é a página que o navegador de uma pessoa desenhou. O querying.ai remove os scripts e só acrescenta o texto de dentro
  das imagens, no texto alternativo delas, o lugar do texto de uma imagem.
* Ela é servida na própria URL da página.
* Ela fica atualizada. O querying.ai a renderiza de novo a cada `refreshDays`, e o módulo para de enviar uma cópia quando ela
  expira.
* Se você editar uma cópia, apenas remova ou corrija: arrume um caractere lido errado, tire um pop-up. Não acrescente
  afirmações, preços ou links que as pessoas não veem.

## O que é a cópia

A cópia é o HTML que o navegador de uma pessoa desenhou depois de rolar a página até o fim: o texto que os scripts
carregaram, a página de cada frame no lugar dele, todos os scripts removidos exceto os dados estruturados (JSON-LD), e o
texto de cada imagem grande acrescentado ao texto alternativo dela. Links e fontes de imagem continuam absolutos, então a cópia
se lê como a página. Ela é a página na última renderização: uma mudança de preço ou estoque chega aos agentes na próxima
renderização, no máximo `refreshDays` depois, mais o tempo até a sua CDN sincronizar. O texto das imagens é copiado como foi
lido, então um caractere lido errado sai assim.

## Cobrança

Uma renderização custa 5 créditos, mais 5 por imagem cujo texto é lido e 2 por imagem que a sua conta leu nos últimos 90 dias
(o mesmo arquivo, lido do mesmo jeito), no máximo 12 imagens. O envio reserva 65 créditos e a conclusão cobra o que a
renderização usou. Uma renderização com falha não é cobrada. Uma atualização custa o mesmo e é cobrada da conta do site; uma
conta sem créditos espera uma hora antes de a página ser tentada de novo. Conectar um site, publicar a cópia de um GEO Score, a
lista de cópias e o download de cópias são gratuitos. Sua CDN envia as cópias, então o tráfego dos agentes não custa nada no
querying.ai. Veja [Créditos e limites](/pt/concepts/credits-and-limits.md).

## Erros

| Status | Código | Causa |
| - | - | - |
| `401` | `UNAUTHORIZED` | A lista de cópias não recebeu chave de site, recebeu uma chave substituída ou a chave de um site removido. |
| `402` | `INSUFFICIENT_CREDITS` | O saldo não cobre a reserva de 65 créditos de uma renderização. |
| `403` | `KEY_SCOPE_DENIED` | A chave de API está limitada a certos motores e `GEO_SCORE` não está entre eles. |
| `404` | `NOT_FOUND` | Nenhum site, página ou GEO Score da sua conta com esse id. |
| `409` | `SITE_EXISTS`, `SITE_LIMIT`, `PAGE_LIMIT`, `TASK_NOT_COMPLETED` | O host já está conectado, a conta tem 20 sites, o site tem 1.000 páginas ou o GEO Score não terminou. |
| `410` | `COPY_EXPIRED` | A cópia do GEO Score expirou. Publique a página pela URL. |
| `422` | `VALIDATION_ERROR`, `NO_RENDERED_COPY` | Um campo não é válido, a URL está em outro host, o GEO Score foi medido de outro país ou não gerou cópia. |
| `429`, `503` | | A concorrência do plano ou a fila de renderização está cheia. Tente de novo depois de `Retry-After`. |

Os endpoints estão na [referência da API](/pt/api-reference/ai-rendering/create-site.md).


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