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

# GEO Overview

> Track how AI answer engines mention your brand: projects, scans, engines, and the visibility dashboard

GEO (generative engine optimization) is the primary product in Notra. It asks AI answer engines the questions your buyers ask, records whether the answer names your brand or a competitor, and shows you where you are missing. The same workspace tracks AI crawler and referral traffic on your site, checks how ready your website is for AI agents, and turns the gaps it finds into content briefs and draft articles.

<Note>
  GEO is included in the Starter, Growth, and Scale plans. On a workspace without one of these plans the GEO pages show an upgrade gate, and the API answers `402` with "GEO plan or available AI credits required".
</Note>

## Projects

All GEO data is scoped to a **project**. A project links a name to one brand identity, so an organization can track several brands or products side by side without mixing prompts, competitors, or scan results. Every GEO page in the dashboard reads the active project from the sidebar's **Projects** menu, and every project-scoped API route takes `projectId` in its path.

* The first project is created for you during onboarding.
* **New project** in the sidebar opens a dialog with two fields: **Project name** and **Brand identity**.
* Deleting a project cascades to its GEO settings, prompts, sequences, competitors, scans, checks, and reports. The organization's last project cannot be deleted.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl https://api.usenotra.com/v1/projects \
  -H "Authorization: Bearer $NOTRA_API_KEY"
```

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://api.usenotra.com/v1/projects \
  -H "Authorization: Bearer $NOTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme", "brandSettingsId": "brand_123" }'
```

`brandSettingsId` is optional; it defaults to the organization's default brand identity. `PATCH /v1/projects/{projectId}` renames a project or relinks its brand identity.

## The GEO sidebar

The dashboard sidebar has two modes, **GEO** and **Studio**. GEO is the default. Its groups are:

| Group      | Pages                                                              |
| ---------- | ------------------------------------------------------------------ |
| Visibility | Overview, Prompts, Competitors, Traffic, Feedback                  |
| Improve    | Content Gaps, Agent Readiness, Write, Content, Schedules, Settings |

The primary action button in GEO mode is **Write**. Utility pages (Integrations, Skills, API Keys) and workspace settings are shared with Studio mode.

<CardGroup cols={2}>
  <Card title="Prompts and Competitors" icon="message-question" href="/geo/prompts-and-competitors">
    Tracked prompts, conversations, bulk import, and competitor tracking.
  </Card>

  <Card title="AI Traffic" icon="chart-line" href="/geo/traffic">
    Install the tracker and see which AI crawlers and referrals visit your site.
  </Card>

  <Card title="Agent Readiness" icon="robot" href="/geo/agent-readiness">
    Score how well AI agents can discover, understand, and use your website.
  </Card>

  <Card title="Content Gaps and Writer" icon="pen" href="/geo/content-gaps-and-writer">
    Find the questions you are missing from and write articles that close them.
  </Card>
</CardGroup>

## How a scan works

A scan is the unit of work behind every visibility number. It runs inside the Notra dashboard, which owns the model credentials and billing gates; the public API only queues scans and reads results.

<Steps>
  <Step title="Build the task list">
    The scan takes every enabled prompt (custom and auto-derived), every enabled engine from your Settings, and every tracked language. Prompts in languages other than English are translated first. Each engine, prompt, and language combination becomes one task; tasks run four at a time.
  </Step>

  <Step title="Ask the engines">
    **Engines** come from the model catalog: a live model list grouped by provider. Selected models that support live web search answer each prompt with retrieval: ChatGPT, Claude, Gemini and Perplexity, plus Google AI Overview and coding agents that research live. Bare catalog models and Cursor are not scanned.
  </Step>

  <Step title="Grounded scanning">
    Grounded engines record the search queries they issued and the sources they cited, so you can see which pages an engine read before it answered. The dashboard labels these results **Search**.
  </Step>

  <Step title="Judge every answer">
    A separate judge model reads each answer and records whether your brand (company name or any alias) is mentioned, at what position, with which sentiment, which tracked competitors appear, and a short excerpt.
  </Step>

  <Step title="Play conversations">
    Multi-turn prompt sequences (called **Conversations** in the dashboard) are replayed turn by turn against the grounded engines during English scans. See [Prompts and Competitors](/geo/prompts-and-competitors#conversations).
  </Step>
</Steps>

### Recurring scans

Scans run on a schedule stored on the project's GEO settings as `scanIntervalHours`. The options are 24, 48, 72, 168, 336, and 720 hours, shown in Settings as **Every day**, **Every 48 hours**, **Every 3 days**, **Every week**, **Every 2 weeks**, and **Every 30 days**. Daily is the default.

Under the hood each project carries a `next_scan_at` due stamp. A cron sweep polls for projects whose stamp has passed, advances the stamp by one interval, and then starts the scan. Because the stamp moves before the scan starts, a failed scan simply waits for the next interval instead of retrying immediately. A scan row that is still marked running after two hours is treated as stuck and marked failed on the next sweep.

Turning **Automatic scans** off in Settings stops the schedule. Manual scans always work: press **Run Scan** on the Overview page (shortcut `R`) or call the API.

### The first scan

During onboarding, saving your brand enables GEO tracking with the daily interval, and the competitors step of onboarding starts your first scan as soon as you finish it. Results usually appear within minutes; the Overview page polls while a scan is running.

### Triggering scans from the API

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://api.usenotra.com/v1/projects/$PROJECT_ID/geo/scans \
  -H "Authorization: Bearer $NOTRA_API_KEY"
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "scanId": "scan_01J...",
  "statusUrl": "/v1/projects/proj_123/geo/scans/scan_01J...",
  "organization": { "id": "org_123", "slug": "acme", "name": "Acme", "logo": null }
}
```

The response is `202 Accepted` and `statusUrl` is also returned as the `Location` header. Poll `GET /v1/projects/{projectId}/geo/scans/{scanId}` until `status` leaves `running` (it becomes `completed` or `failed`). While a scan for the project is in flight the trigger endpoint answers `409`. This endpoint allows 4 requests per hour per organization.

## What the Overview page shows

The Overview page (`/geo`) is titled **GEO** with the subtitle "How AI engines talk about" your company name. The range picker offers **Today**, **Yesterday**, **Last 7 days**, **Last 14 days**, **Last 30 days** (default), **Last 90 days**, and **Year to date**. A **GEO sections** switch flips between three views.

### Visibility

* **Mentions**: a per-provider mention rate card with the delta against the first half of the range.
* **Mention activity**: the daily mention trend across all providers, with a per-provider breakdown.
* **Mention rate by engine**: a table with **Engine**, **Mentions**, **Mention rate**, **Avg position**, **Last checked**, and **Trend** columns. Rows group into provider families.
* **Share of voice**: your brand against tracked competitors, as a donut and a table, with the top five brands broken out and the rest grouped under Other.
* **Performance by language**: **Language**, **Mention rate**, and **Trend** for each tracked language.

### Brand Sentiment

The **Brand Sentiment** dashboard tab uses one compact dualtone card:
score and current-period chart on the left, positive/negative themes and sentiment distribution on the right.
The summary shows **Current sentiment score**, its change from the previous period,
and a 0–100 score-position scale. Hover or focus the black marker to see the current
score. Unrated answers and answers without a brand mention are excluded.
The chart and summary stack on mobile. Loading uses layout-preserving skeletons;
empty states distinguish no saved answers, no rated mentions and no supported themes.

Below, a single Feedback-style table lists claims with their theme beneath the claim,
sentiment, models and distinct supporting-answer counts. It shows the analyzed evidence
sample, not all responses in the selected period. Select a row to open that claim's
original quotes, prompts, models and timestamps. On mobile, sentiment appears within
the claim cell and model details remain available in the sheet.
The trend uses the Mention Activity chart with monotone curves and a subtle current-period
fill on a fixed 0–100 scale with gaps for missing
ratings. Point markers appear when a rated day has no rated neighbor, so isolated
observations remain visible. Contiguous series use a clean line. If the selected window
ends with missing days, a dashed line extends the last observed
score for up to three days, using the same linear-trend calculation as Mention Activity.
It requires at least three completed observed days and stays within 0–100. Internal
gaps remain empty. Tooltips label the estimates separately; they do not affect scores,
comparisons, mention counts or saved data.
The previous period covers the same number of UTC calendar days immediately before the
selected inclusive window. The shared dashboard date filter controls the chart;
the previous period is used for the score comparison rather than a second plotted line.
Focus or hover the score comparison to see both full date windows.
The delta is in **score points**, not a percent change. It is unavailable when either
period has no classified mentions. Unspecified windows default to 30 UTC calendar days
including today. Explicit windows support up to 366 days.
It replaces the dashboard's former Prompts tab. Open it directly with `?tab=brand-sentiment`.
Existing `?tab=sentiment` links open the same view and update to the canonical tab name.
Legacy `?tab=prompts` links fall back to **Visibility**. Prompt management remains
on the standalone `/geo/prompts` page.

Brand sentiment uses saved **English, single-turn answers** for the active organization,
project and date range. It describes the AI-assessed tone toward your brand, not the
overall mood of an answer or customer satisfaction.

The score is **(positive × 100 + neutral × 50) / classified mentions**.
All positive mentions score 100, all neutral mentions score 50, and all negative mentions
score 0. Three positive, two neutral and one negative mention produce a score of 66.7,
displayed as **67 / 100**. It is a descriptive rating, not a percentage or model confidence.

Only mentions with an exact positive, neutral or negative label enter the denominator.
Unknown labels and answers that do not mention your brand are excluded. With no classified
mentions, the score is unavailable, shown as **—**, rather than zero. The existing
`positiveShare` response field remains positive / classified mentions, which is 50% in
the example above. It is distinct from the score.

Daily buckets use UTC. Counts across engines are combined before calculating the
score, rather than averaging model scores. Days without classified
mentions remain gaps. The date picker's final day is included; internally the window ends
at the start of the following UTC day.

Each saved answer has equal weight. Repeated manual scans count again, and partial results
from unfinished scans may appear.
Changes to the prompt mix, engine mix or judge can change the distribution without showing
a real change in brand perception. This is not NPS, a confidence score or a causal trend.

Answer details show a compact sentiment label. An emphasized excerpt appears only when
it is an exact, contiguous substring of the saved answer. Paraphrased or combined excerpts
are not emphasized. The original answer renderer is preserved. Viewing sentiment uses
existing labels and does not run new AI calls or reclassify older answers.

#### Agent-extracted themes

Select **Find themes** in the themes empty state to extract themes from saved positive
and negative answers. The action appears when analysis is missing or stale, with a usage
notice before the paid action. Failed analysis offers a retry; ready results have no
persistent generation button. Configuration problems show the server's explanation.
This is a separate structured model call through the organization's existing model router,
using `zai/glm-5.3-flash`. It has no browsing or other tools. Each attempted call uses
one AI-answer quota unit or the existing AI-credit billing path, including failed output
validation. The existing sentiment judge and its labels are unchanged.

Sampling is deterministic: up to 12 checks per polarity, ordered by a hash of the saved
check ID, with the first 2,000 answer characters and up to 500 prompt characters per check.
The model may return up to six themes with one to four concrete claims per theme,
each with up to six evidence references. Every quote must be an exact contiguous substring
of the supplied historical answer and match the theme's saved polarity. Foreign IDs,
paraphrased quotes, duplicate sources and mismatched polarities invalidate the result.
Answers and brand metadata are treated as untrusted data, never instructions.

Select a claim to see its saved quotes, engines, dates and prompts. **Recurring** means
at least two distinct sampled checks support it. **Single source** means one check.
These evidence counts are not population prevalence or counts of independent users.
The classified-mention distribution uses all scoped saved counts, not the theme sample;
neutral is never combined with negative, and displayed percentages are rounded.

Analysis reads never generate content or write the cache. An authorized POST mutation
awaits the bounded extraction, with a 60-second model timeout and no automatic model retries.
Redis stores results for seven days under organization, resolved project, UTC window,
model, brand name and historical-input fingerprint. The fingerprint includes
the canonical project's `geo_settings.companyName`, matching the scan and judge context,
rather than the linked content brand identity's name. It also includes
all eligible answer content, even outside the bounded sample. A finalized scan invalidates
the client's analysis query; changed inputs show an analysis action on the next read.
That lookup is marked stale and the themes section offers **Find themes** again.
Completed dashboard scans start a separate background workflow for the last 30 days.
It generates only when new eligible answers exist and no fresh result is available.
A persisted atomic timestamp allows at most one automatic attempt per project in 24 hours.
This is always enabled; manual analysis for other date windows still requires confirmation.

The UI handles ready, pending, stale, failed and unavailable states. A 180-second Redis
lease prevents concurrent extraction for the same project and window, even if inputs change. The worker checks ownership after
billing reservation and uses an atomic token check when publishing. Old results cannot
overwrite another worker's result or appear under a newer input fingerprint. Expired work
can be retried. Missing Redis, provider configuration or a project brand name makes analysis
unavailable. Migration `0088_geo_sentiment_attempt` adds the persisted attempt timestamp
and removes the obsolete opt-in flag if present. Cached results are not
permanent Postgres records. Older results for the same window can remain visible while a
refresh is pending, but are not treated as fresh.

Quotes are mechanically validated; theme interpretation still depends on the model and
the saved judge labels. A bounded sample can miss themes or repeat similar answers.

### Journeys

The sessions AI agents took through your site, fed by the AI traffic tracker: median depth, journeys per source, most fetched pages, and the **Agent journeys** table. See [AI Traffic](/geo/traffic#journeys).

## Reading visibility from the API

Every visibility endpoint accepts `days` (1 to 365) for a rolling window or `from` and `to` (`YYYY-MM-DD`) for an explicit one. `from`/`to` win when both are present.

| Endpoint                                  | Returns                                                                                                                                                   |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /geo/visibility/overview`            | `engines[]` with `engine`, `checks`, `mentions`, `mentionRate`, `avgPosition`, `lastCheckedAt`                                                            |
| `GET /geo/visibility/timeseries`          | `points[]` with one `day` and `engine` per row plus `checks`, `mentions`, `avgPosition`                                                                   |
| `GET /geo/visibility/prompt-results`      | `results[]` with `promptId`, `engine`, `prompt`, `answer`, `mentioned`, `position`, `sentiment`, `excerpt`, `searchQueries`, `sources[]`, `lastCheckedAt` |
| `GET /geo/visibility/competitor-share`    | `points[]` with `brand`, `mentions`, and a per-brand `trend`, plus the daily `timeseries`                                                                 |
| `GET /geo/visibility/language-share`      | `points[]` with `language`, `checks`, `mentions`, `mentionRate`, `avgPosition`, `trend`                                                                   |
| `GET /geo/visibility/competitors/{brand}` | daily `points[]` and the `prompts[]` that produced them for one brand                                                                                     |

All paths are prefixed with `/v1/projects/{projectId}`.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const response = await fetch(
  `https://api.usenotra.com/v1/projects/${projectId}/geo/visibility/overview?days=30`,
  { headers: { Authorization: `Bearer ${process.env.NOTRA_API_KEY}` } }
);
const { engines } = await response.json();
for (const engine of engines) {
  console.log(engine.engine, `${Math.round(engine.mentionRate * 100)}%`);
}
```

Each response also carries `configured`. When it is `false`, the analytics backend is not available for the deployment and the payload is empty rather than an error.

## API scopes

GEO routes use granular scopes in the form `<resource>.read` and `<resource>.write`. The resources are `projects`, `geo-settings`, `prompts` (which also covers sequences), `competitors`, `scans`, `visibility`, `briefs` (which also covers gaps), `agent-readiness`, and `traffic` (which also covers the organization-level ingest token routes). The legacy `api.read` and `api.write` scopes expand to every read or every scope. Create keys under **API Keys** in the sidebar; see [Authentication](/api/authentication).

<CardGroup cols={2}>
  <Card title="API Reference: GEO" icon="code" href="/api-reference/geo/list-geo-projects">
    Every GEO endpoint with request and response schemas.
  </Card>

  <Card title="Rate limits" icon="gauge" href="/api/rate-limits">
    Per-endpoint limits for scans, imports, sequences, and the writer.
  </Card>
</CardGroup>
