Copy & paste prompt
Run this in Claude
Build me an interactive HTML dashboard called "Domain Report" for **{{BRAND}}** (domain: **{{DOMAIN}}**).
Save it as a Cowork artifact with id `domain-report-{{slug}}` so I can re-open it later. Bake the data into the HTML (the artifact sandbox has no external HTTP) — to refresh, I'll re-run this prompt.
Use a two-tab layout at the top:
- **Tab 1: AI Search** — data from the OtterlyAI public API
- **Tab 2: Organic Search** — data from the Ahrefs MCP
Light mode, Chart.js for charts, plain HTML tables for lists. Match the visual feel of the existing OtterlyAI brand (warm red `#d14d3d` as the accent, white cards on a light grey background, 12px rounded corners, subtle borders).
---
### Sources
- **OtterlyAI brand report ID:** `{{OTTERLY_REPORT_ID}}`
- **OtterlyAI workspace ID:** `{{OTTERLY_WORKSPACE_ID}}`
- **Ahrefs target:** `{{DOMAIN}}` in `subdomains` mode
- **Window:** last 30 days (current period) vs the 30 days before (previous period) — compute the exact dates from today
---
### Tab 1 — AI Search (7 widgets)
**1. Domain coverage over time (line chart)**
Series: `otterly`-style brand coverage of `{{DOMAIN}}` per day across the last 30 days. Pull from `GET /v1/reports/brand/{reportId}/stats`, read `allBrandsAnalysis.domainCoverageHistory[]`, and for each date pick the entry where `isMainBrand: true`. Add two dropdowns above the chart:
- AI search engine: `All engines`, `ChatGPT`, `Gemini`, `Google AI Overviews`, `Google AI Mode`, `Perplexity`, `Copilot`
- Country: every country configured on the brand report
Pre-fetch one stats call per (country × engine) combo so the dropdowns swap instantly. Show a KPI strip above the chart for the active slice: Domain coverage %, Share of voice %, Avg rank, Total mentions.
**2. Top URLs (list)**
Most-cited URLs of `{{DOMAIN}}` in the current window. Pull from `GET /v1/reports/brand/{reportId}/citations/stats`, use `domainCitations.mostCitedUrls[]`. Show top 10 with rank + citation count.
**3. Winner URLs (list)**
URLs in `domainCitations.mostCitedUrls[]` that got more citations in the current window than the previous one. Call `/citations/stats` twice (current + previous), diff by URL, sort by `delta` descending. Show top 10. Each row: URL, +delta, `previous → current`.
**4. Loser URLs (list)**
Same as winners but with negative deltas, sorted ascending.
> Render 2, 3, 4 as a single card with three pill tabs (Top / Winners / Losers) and a shared list area underneath.
**5. On-page recommendations (table, top 5)**
Pull `GET /v1/reports/brand/{reportId}/recommendations?country={{PRIMARY_COUNTRY}}`. Filter to `type === 'create_content_on_page'` (or `'improve_existing_content'` if you want both). Sort by `score` descending, take top 5. Columns: suggested page / prompt, suggested headline, engine, citations, score.
**6. Crawlability audit (bot list)**
Use the latest completed `crawlability-check` for `{{DOMAIN}}` from `GET /v1/audits/geo/crawlability-checks?workspaceId={workspaceId}`. If none exists, trigger one with `POST /v1/audits/geo/crawlability-checks` and poll. For each AI bot (union of `robotsTxtAnalysis` and `serverBotAccess`), show Yes (green pill) only if both robots.txt allows it AND `serverBotAccess[bot].ok === true`. Two-column grid.
**7. Content audit — top 3 winners vs top 3 losers**
Take the top 3 winners and top 3 losers from widgets 3 and 4. For each URL, find a recent completed content check via `GET /v1/audits/geo/content-checks?workspaceId={workspaceId}`. If none exists, trigger `POST /v1/audits/geo/content-checks` for each (6 quota slots) and poll until completed. Show:
- A bar chart comparing the averages of Winners vs Losers across the four `categoryScores`: Metadata, Technical, Structure, Content (0–100 each).
- A KPI strip above the chart: winners count + avg overall, losers count + avg overall, gap.
- A table below the chart with one row per URL: group (Winner/Loser pill), URL, overall score, the four category scores, and dynamic content score. Color-code each score green ≥ 80, amber 60–79, red < 60.
---
### Tab 2 — Organic Search (5 widgets)
**O1. Average organic traffic over time (line chart)**
Pull `site-explorer-metrics-history` with `target={{DOMAIN}}`, `mode=subdomains`, monthly grouping, ~24 months back. Select `date,org_traffic`. Add a country selector with `All countries` + at least US, DE, GB, IN — repeat the call per country (`country` param). KPI strip above the chart: latest month, 3-month trend %, peak.
**O2. Branded vs non-branded traffic (line chart, stacked or toggleable)**
Two series, monthly for the last 12–24 months:
- Branded = sum of `org_traffic` over keywords whose text contains `{{BRAND}}` (case-insensitive)
- Non-branded = the rest
Easiest path: call `site-explorer-organic-keywords` once per month-end snapshot with `select=keyword,sum_traffic` filtered by `where: {"field":"keyword","is":["isubstring","{{BRAND}}"]}` for branded, and `{"not":{...same...}}` for non-branded. Or aggregate keywords once and bucket by month if `metrics-history` supports a keyword filter. Add a dropdown to switch between Both / Branded only / Non-branded only. KPI strip: branded share %, non-branded share % (latest month).
**O3. Top non-branded keywords (table)**
`site-explorer-organic-keywords` with `target={{DOMAIN}}`, current date, `order_by=traffic:desc`, `limit=25`, and `where: {"not":{"field":"keyword","is":["isubstring","{{BRAND}}"]}}`. Select `keyword,country,position,volume,traffic,clicks,cpc`. Columns: Keyword, Country, Position, Volume, Est. traffic, Clicks, CPC ($).
**O4. Top performing URLs (table)**
`site-explorer-top-pages` with `target={{DOMAIN}}`, `order_by=traffic:desc`, `limit=20`. Select `url,traffic,keywords,value`. Columns: URL, Traffic, Keywords, Value ($).
**O5. New backlinks — last 30 days (table)**
`site-explorer-all-backlinks` with `target={{DOMAIN}}`, `history=since:{{30_DAYS_AGO_YYYY-MM-DD}}`, `aggregation=1_per_domain`, `order_by=domain_rating_source:desc`, `limit=25`, and `where: {"field":"first_seen","is":["gte","{{30_DAYS_AGO_YYYY-MM-DD}}"]}`. Select `url_from,domain_rating_source,first_seen,link_type`. Columns: Referring page, DR, First seen, Type.
---
### Implementation notes
- Save the HTML to your outputs folder as `domain_report.html`, register it with `mcp__cowork__create_artifact` (id `domain-report-{{slug}}`).
- Inline all CSS + JS. Load Chart.js from the allowed CDN (`https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.js`) — and only that.
- Probe each endpoint once with a small call before fetching the full cube so you confirm the response shape (Otterly's `domainCoverageHistory[]` is nested under `allBrandsAnalysis`, Ahrefs returns flat arrays).
- Before triggering any GEO audits on Otterly, check workspace `geoAuditUsedCount` vs `geoAuditMaxCount`. Before fetching from Ahrefs, check `units_limit_workspace` and `units_usage_workspace` via `subscription-info-limits-and-usage` — the **workspace** cap is the binding one, not the per-API-key cap.
- If a content check or content audit hasn't completed within ~30 seconds, poll again — fresh ones typically take 30–90s.
- Add a verification step at the end: parse the embedded JSON, count rows per widget, confirm all 12 widgets have data (or a labeled empty state).
Tip: replace anything in [BRACKETS] with your own values, then paste this into Claude with the OtterlyAI connector enabled.
Ready to deploy this Prompt?
Start a free OtterlyAI trial — no card required.