> ## 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.

# GEO Score: see how much of a page ChatGPT search keeps

> Score one page on how much of what a person reads on it ChatGPT search keeps and ChatGPT receives when it opens the page, and see what keeps the rest out: robots.txt, a refused crawler, content loaded after the page opens and text inside images.

Use GEO Score to check a page you want ChatGPT to cite. The score is the share of what a person reads on the
page, the text inside its images included, that ChatGPT search keeps in its copy of the page. `direct` gives the
share ChatGPT receives when it opens the page itself, as when someone gives it the URL. Every piece ChatGPT search
does not keep comes with the reason, and `analysis` turns the reasons into fixes.

## Score a page

```bash theme={"theme":"css-variables"}
curl -X POST https://api.querying.ai/v1/geo-score \
  -H "Authorization: Bearer $QUERYING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://global.oliveyoung.com/product/detail?prdtNo=GA240322874", "country": "US" }'
```

The task is accepted and queued:

```json theme={"theme":"css-variables"}
{
  "success": true,
  "task": {
    "id": "0b6c1f7e-...",
    "taskType": "GEO_SCORE",
    "status": "QUEUED",
    "priority": 1,
    "createdAt": "2026-10-21T09:00:00.000Z"
  },
  "credits": { "creditsToCharge": 0, "creditsCharged": 0 }
}
```

Read the result with `GET /v1/async/task/{id}`, or add `webhook.url` to the request and receive it
when the task finishes. Most pages take 1 to 3 minutes, so poll every 10 to 15 seconds. The task works like
any other [async task](/concepts/async-tasks.md), and the result is in `response`. The request schema is in the
[API reference](/api-reference/geo-score.md).

## Request fields

| Field | Required | Description |
| - | - | - |
| `url` | Yes | The page to score: an `http` or `https` URL on a public host and the default port, up to 2,048 characters. |
| `country` | No | Where the reader is: `US` (default) or `KR`. The page is rendered from an address in that country, in its language and time zone. |
| `idempotencyKey` | No | Makes a retried submit safe. See [Idempotency](/concepts/idempotency.md). |
| `priority` | No | Integer `1`–`10`, default `1`. Out-of-range values are clamped. Echoed in task metadata; it does not guarantee execution order. See [Async tasks](/concepts/async-tasks.md). |
| `webhook.url` | No | Where to POST the finished task. See [Webhooks](/concepts/webhooks.md). |

Any other field returns `400 VALIDATION_ERROR`.

## How a page is scored

1. **Access.** robots.txt is read for OAI-SearchBot, ChatGPT-User and GPTBot. The page is fetched as each of
   them and as a browser, from a US address, where ChatGPT-User fetches pages from.
2. **What a person reads.** A browser in `country` renders the page and scrolls it to the end, and the
   text in its large images is read line by line. Lines about the page's own subject become pieces. Menus,
   footers, other products, customer reviews, buttons and ads stay out of the score.
3. **What ChatGPT search keeps.** A browser renders the page as OAI-SearchBot, the crawler that fills ChatGPT
   search, from a US address, and lets no request for data out. Its copy is the text of the HTML the server
   sends and the text the page's scripts draw from it, hidden text and image alt text included. Text inside
   images and content the page loads after it opens are not in it, as they are not in ChatGPT search.
4. **What ChatGPT receives when it opens the page.** The HTML ChatGPT-User receives is read the way ChatGPT's
   reader reads it: its text, hidden text and image alt text, without scripts. An image counts when ChatGPT
   can open and read it: the HTML gives the picture as an `img` src, as JPEG, PNG or WebP, at a size whose
   text stays legible.
5. **Whether ChatGPT opens the page.** Production ChatGPT is asked twice to open the page. Its answers are not
   scored: they show only whether it opened the page.
6. **Placing each piece.** A piece is `read` when the search copy holds it, and `missing` with its reason
   otherwise. `direct` places it the same way in what ChatGPT receives when it opens the page. Code decides
   every piece, so a page that has not changed gets the same score.

## Read the result

The OLIVE YOUNG page above reads like this, trimmed to the main fields:

```json theme={"theme":"css-variables"}
{
  "level": "url",
  "url": "https://global.oliveyoung.com/product/detail?prdtNo=GA240322874",
  "finalUrl": "https://global.oliveyoung.com/product/detail?prdtNo=GA240322874",
  "score": 29,
  "direct": { "score": 29, "read": 16 },
  "pageType": "product",
  "access": {
    "verdict": "open",
    "chatgpt": { "opened": true, "inSearch": false, "lines": 819, "searchedFor": [], "observedAt": "2026-10-21T17:35:37.050Z" },
    "search": { "refused": false, "lines": 1343 },
    "robots": {
      "OAI-SearchBot": { "allowed": true, "named": false },
      "ChatGPT-User": { "allowed": true, "named": false },
      "GPTBot": { "allowed": true, "named": false }
    },
    "noindex": { "noindex": false },
    "probes": [
      { "agent": "ChatGPT-User", "status": 200, "blocked": false },
      { "agent": "OAI-SearchBot", "status": 200, "blocked": false },
      { "agent": "browser", "status": 200, "blocked": false }
    ]
  },
  "reading": {
    "score": 29,
    "facts": 55,
    "read": 16,
    "locations": { "html": 16, "rendered_only": 13, "image": 26 },
    "items": [
      {
        "label": "COSRX Vitamin E Vitalizing Sunscreen SPF 50+",
        "value": "COSRX Vitamin E Vitalizing Sunscreen SPF 50+",
        "where": "html",
        "line": 1,
        "status": "read",
        "direct": { "status": "read" }
      },
      {
        "label": "US$24.00",
        "value": "US$24.00",
        "where": "rendered_only",
        "status": "missing",
        "reason": "javascript_only",
        "direct": { "status": "missing", "reason": "javascript_only" }
      },
      {
        "label": "Strong and effective sun protection",
        "value": "Strong and effective sun protection",
        "where": "image",
        "status": "missing",
        "reason": "image_only",
        "direct": { "status": "missing", "reason": "image_only" },
        "foundIn": "image https://cdn-image.oliveyoung.com/slicedImg/editor/1157/f9e08334-....jpg (no alt text): the HTML ChatGPT receives has no img with this picture as its src"
      }
    ]
  },
  "causes": [
    { "reason": "javascript_only", "labels": ["4.6", "21 review", "30%", "US$24.00", "US$16.80", "..."] },
    { "reason": "image_only", "labels": ["Strong and effective sun protection", "Key Ingredients", "..."] }
  ],
  "analysis": {
    "summary": "ChatGPT search keeps 16 of 55 facts of the page. The biggest issue: 26 facts are written only inside an image, among them Strong and effective sun protection; Key Ingredients; Tocopherol (Vitamin E) (47 points). Fixing the confirmed issues could raise the score to about 100.",
    "potential": 100,
    "direct": "When ChatGPT opens the page itself, as when a person gives it the URL, it receives 16 of 55 facts (score 29).",
    "issues": [
      {
        "code": "image_only",
        "basis": "confirmed",
        "points": 47,
        "title": "26 facts are written only inside an image",
        "detail": "People read these values in the page's image, but the HTML has no text for them. ChatGPT search keeps a page's text and its images' alt text, not the text inside its images.",
        "fix": "Write this content as text in the page's HTML, next to the image or in place of it (a size chart as a table, rules and notes as sentences), or at least in the image's alt text.",
        "facts": [{ "label": "Key Ingredients", "value": "Key Ingredients", "foundIn": "image https://cdn-image.oliveyoung.com/... (no alt text): the HTML ChatGPT receives has no img with this picture as its src" }],
        "evidence": ["image https://cdn-image.oliveyoung.com/... (no alt text): the HTML ChatGPT receives has no img with this picture as its src"]
      },
      { "code": "javascript_only", "basis": "confirmed", "points": 24, "title": "13 facts are loaded only after the page opens", "...": "..." }
    ]
  },
  "reference": "rendered"
}
```

### `score`

The share of pieces ChatGPT search keeps, from 0 to 100. Every piece weighs the same. The score is 0 when
robots.txt keeps the page out of ChatGPT search and when the site refuses ChatGPT search's crawler.
`reading.score` holds the share before robots.txt sets the score to 0.

### `direct`

What ChatGPT receives when it opens the page itself, as when someone gives it the URL. `direct.score` is the
share of pieces in the HTML ChatGPT-User receives and in the images ChatGPT can open, and `direct.read` counts
them. It is 0 when ChatGPT could not open the page. The two scores part where a page writes text in images or
draws it with scripts: ChatGPT search keeps no text inside images and keeps what scripts draw from the HTML,
while ChatGPT's reader opens images it can read and runs no scripts. A grocery product page whose nutrition
label is a full-size JPEG scored 56 for search and 93 opened directly.

### `access`

| `verdict` | Meaning |
| - | - |
| `open` | ChatGPT opened the page. `chatgpt.lines` is the number of lines its reader made of it. |
| `search_only` | ChatGPT could not open the page and found it only among its search results. `direct.score` is 0. |
| `blocked` | ChatGPT could not open the page and did not find it in search. `direct.score` is 0, and `blockedBy` names who refused it. |
| `search_opt_out` | robots.txt keeps the page out of ChatGPT search, so the score is 0. |

`search.refused` is `true` when the site refuses ChatGPT search's crawler, which holds the score at 0, and
`search.lines` counts the lines of the search copy. `blockedBy` is `user_agent` when the site refuses the
ChatGPT-User agent from any address, `openai_addresses` when it serves that agent from our address and refuses
OpenAI's, and `site` when it refuses a browser too. `robots` gives the deciding robots.txt line for each OpenAI
agent, `noindex` reports a noindex directive and where it was set, and `probes` lists the status each fetch received.

### `reading.items[]`

One entry per piece:

| Field | Description |
| - | - |
| `label`, `value` | The piece as the page shows it. |
| `where` | Where the page has it: `html`, `structured_data`, `script`, `rendered_only`, `frame` or `image`. `rendered_only` is on the rendered page and in no HTML ChatGPT receives. |
| `status` | `read` when ChatGPT search keeps it, or `missing`. |
| `reason` | Why ChatGPT search does not keep it (below). |
| `direct` | `status` and `reason` for what ChatGPT receives when it opens the page. |
| `rendering` | `status` in the AI-ready copy of the page: `read` when the copy holds the piece in its text. |
| `foundIn` | Where the page has a missing piece, such as a line of the HTML, or an image URL and why ChatGPT cannot read the image. |

`reading.locations` counts the pieces by `where`, which shows what remains to fix once ChatGPT can open the
page. `causes[]` groups the missing pieces by reason.

| `reason` | Meaning | Fix |
| - | - | - |
| `blocked` | The site refuses ChatGPT search's crawler. In `direct`, the piece is in the HTML or an image ChatGPT can read, and ChatGPT could not open the page. | Let OAI-SearchBot and ChatGPT-User through, by user agent and OpenAI's published addresses. |
| `javascript_only` | The page loads the piece after it opens. In `direct`, the piece shows only after scripts run. | Put it in the HTML the server sends, rendered on the server or at build time. |
| `structured_data_only` | The piece is only in JSON-LD or meta tags. | Show it in the page's text as well. |
| `image_only` | The piece is written only inside an image, and ChatGPT search keeps no text inside images. In `direct`, ChatGPT cannot read the image: the HTML does not give the picture as an `img` src, the src is a smaller file, or it is served as AVIF. | Write it as text in the HTML or in the image's alt text. |

### `analysis`

`summary` states the score, the largest issue and the score with the confirmed issues fixed (`potential`).
`direct` says what ChatGPT receives when it opens the page itself, or who keeps it from opening the page and
the fix. Each entry of `issues[]` has the cause (`detail`), the fix (`fix`), the pieces it costs (`facts`), its
evidence and the points it takes off the score. Access issues that hold the score at 0 come first, then the
rest by points. `basis` is `confirmed`: our own reading of the page (its HTML, renders, images, robots.txt, HTTP
answers) shows the issue. The text of `analysis` is in English.

### `markdown`

The page a person sees, as Markdown, with the text of its images where the images sit. The page's own
content, which the score checks, stands under its headings and labels. The menus, reviews and other parts around it
are folded to one line each. Each piece ChatGPT search does not keep is marked where it sits, `_(missing: …)_`
with the reason, and the header gives how many pieces ChatGPT receives when it opens the page. The header and the
marks are in the page's language: English, Korean, Japanese, Chinese, Spanish, French, German or Portuguese, and
English for any other. A Korean page reads `_(누락: …)_`. An excerpt of the OLIVE YOUNG page, which is in English:

```markdown theme={"theme":"css-variables"}
4.6 _(missing: loaded after the page opens)_
21 review _(missing: loaded after the page opens)_
US$16.80 _(missing: loaded after the page opens)_

> **Text in image** f9e08334-9760-45c5-89bb-…66eb1752.jpg · 52 lines · ChatGPT search keeps 10 of its 37 pieces
>
> Strong and effective sun protection _(missing)_
> Key Ingredients _(missing)_
> Tocopherol (Vitamin E) _(missing)_

_Other items (112 lines), reviews and questions (90), site menus and notices (57), buttons and tabs (46): Most Helpful · filter · Best …_
```

### `screenshot`

The first four screens of the page as a person sees it, as a JPEG: a 1366×900 window, taken at the top once the
page was scrolled to the end. `url` is its own access key and opens the file until `expiresAt`, 30 days after the
task. A page whose picture could not be taken comes without `screenshot`, and a picture that could not be stored
has `status: "failed"`.

### `rendering`

The page rendered for AI agents. The copy is the HTML a person's browser drew, without scripts, with the text of
each image added to its alt text, the text alternative WCAG asks of an image of text. `rendering.score` is the
share of pieces the copy holds in its text: what an AI agent served the copy receives without opening an image.
`rendering.read` counts them and `reading.items[].rendering` marks each piece; the copy itself is not part of
the result. The OLIVE YOUNG page above holds all 55 of its pieces in its copy, a score of 100: the text of
its ingredient and FAQ images as alt text, and the prices and ratings its scripts load.

```json theme={"theme":"css-variables"}
{
  "screenshot": { "status": "ok", "url": "https://api.querying.ai/v1/evidence/1792627200-0f1e….jpg", "expiresAt": "2026-11-21T04:05:43.048Z" },
  "rendering": {
    "score": 100,
    "read": 55
  }
}
```

`rendering` is absent when no browser could show the page or its drawn HTML is too large to copy.

## Reading the score correctly

* ChatGPT search works from a copy its crawler made of the page: the text of the HTML and what scripts draw from
  it, without the text inside images or what the page loads after it opens. The copy dates from the last crawl,
  so a change reaches ChatGPT search after the crawler fetches the page again.
* When ChatGPT opens a page itself, it reads the HTML the server sends. It runs no scripts, reads image alt text,
  and opens an image only through the `img` src that HTML gives. OpenAI reuses a page it opened for about 30
  minutes.
* The score moves only when the page or the way it renders changes. Of fifteen pages scored three times each,
  ten moved 3 points or less. A page that draws different parts on each visit moves more.
* GEO Score covers what ChatGPT has of one page. Which pages ChatGPT search picks for a question is what
  [GEO monitors](/monitors.md) track.

## Errors and billing

A completed task costs 10 credits for the page and 5 for each image whose text is read, at most 12 images: `images.read`
in the result. An image the host refused or that could not be read, counted in `images.unread`, is free. An image your account read in the last 90 days is reused instead, counted in `images.reused`, for 2 credits. Submitting holds
70 credits, the most a page costs, and completion charges what the task used. A failed task is not charged. See
[Credits and limits](/concepts/credits-and-limits.md).

### Rejected at submit

| Status | Code | Cause |
| - | - | - |
| `400` | `VALIDATION_ERROR` | The body is not a JSON object, has a field other than those above, or `idempotencyKey`, `priority` or `webhook` has the wrong type. |
| `400` | `USE_DEDICATED_ENDPOINT` | `GEO_SCORE` was sent to `/v1/async/task`. |
| `402` | `INSUFFICIENT_CREDITS` | The balance is below 70 credits, the most a page costs. |
| `409` | `RESOURCE_ALREADY_EXISTS` | A task with this `idempotencyKey` already exists. |
| `422` | `VALIDATION_ERROR` | `url` is missing, is not `http` or `https`, or names a private host or port. `details` names the field. |
| `422` | `REGION_UNSUPPORTED` | `country` is not `US` or `KR`. |

Authentication, concurrency and rate errors are the same as for any task. See [Errors](/concepts/errors.md).

### Failed tasks

A failed task has `status: "FAILED"` and an `error` string that starts with its code:

```json theme={"theme":"css-variables"}
{
  "success": true,
  "task": { "id": "0b6c1f7e-...", "taskType": "GEO_SCORE", "status": "FAILED", "...": "..." },
  "credits": { "creditsToCharge": 0, "creditsCharged": 0 },
  "error": "PAGE_UNAVAILABLE: No browser could show the page (bot wall)"
}
```

| Code | Meaning | What to do |
| - | - | - |
| `PAGE_UNAVAILABLE` | No browser could show the page, as a person or as ChatGPT search's crawler: a bot check, an error status or a page that does not exist. The message gives the reason. | Check that the URL opens in a browser. The same URL fails the same way again. |
| `CHATGPT_UNAVAILABLE` | ChatGPT answered none of the requests to open the page in time. | Send the task again. |
| `NO_IP_AVAILABLE` | No capacity was available in time. | Retry later. |
| Any other code | The page could not be scored. | Retry. |

## Next steps

> **[Citation Analysis](/research/citation-analysis.md)**
>
> See which sources shaped one answer.

> **[GEO monitors](/monitors.md)**
>
> Track your brand across AI engines on a schedule.


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