Overview
OLAB Assist gives agencies five ways to use their monitoring data outside the dashboard. All of them read the same data and follow the same versioned contract, so what you see in one matches the others.
- REST API: pull sites, scores, findings, AI answers, score history, trends and changes into a CRM, a data warehouse or a reporting tool.
- Webhooks: get a signed JSON event the moment a scan finishes or new AI answers arrive, so nothing has to poll.
- MCP server: connect Claude, ChatGPT, Cursor or any Model Context Protocol client and ask about your sites in plain language.
- Looker Studio: build client reports on scores, issues and AI visibility with the OLAB Assist connector.
- Notion: keep a Notion page with your sites, issues and AI answers up to date, without code.
Prefer a walkthrough? The integration guides set up each one step by step: Notion, Looker Studio, Slack, Discord, Zapier and Make, REST API and MCP.
The API, webhooks, MCP, Looker Studio and Notion are included in the Agency plan. The account owner manages keys and webhooks in Integrations.
Authentication
Create a key in Settings. The full key is shown once; we store only a hash of it. Send it with every request:
curl https://api.olabassist.com/api/v1/sites \
-H "Authorization: Bearer olab_YOUR_KEY"
Keys are read-only and scoped to your workspace: a key only ever sees the sites in the account that created it. Revoke a key in Settings and it stops working immediately. If the account leaves the Agency plan, its keys stop working until it returns.
Requests and limits
- Base URL:
https://api.olabassist.com/api/v1.GET /api/v1returns the list of endpoints. - All endpoints use
GETand return JSON:{ "data": …, "api_version": "v1" }. Lists that page also returnnext_cursor. - Timestamps are ISO 8601 in UTC. Scores are 0 to 100.
- Rate limit: 120 requests per minute per key. Above it you get
429withretryAfterSeconds. - Wherever a path has
{site}you can pass the site id or its domain, e.g./sites/example.com.
Endpoints
GET /account
Your plan, limits and usage.
GET /sites
Every monitored site with its latest score and open issues.
{
"data": [{
"id": "k3Jd9x2Lq",
"domain": "example.com",
"brand_name": "Example",
"status": "active",
"created_at": "2026-09-01T10:00:00.000Z",
"next_scan_at": "2026-10-12T10:00:00.000Z",
"last_checked_at": "2026-10-05T10:00:00.000Z",
"score": { "overall": 92, "seo": 95, "geo": 88, "aeo": 90, "aio": 94 },
"issues": { "critical": 0, "warnings": 2 },
"regressions_last_7_days": 0,
"report_url": "https://olabassist.com/r/…",
"app_url": "https://olabassist.com/app/?site=k3Jd9x2Lq"
}],
"api_version": "v1"
}
GET /sites/{site}
Everything above plus ai_visibility (answers checked, how many named you, how many cited your site, your rank among tracked brands, sentiment), share_of_voice per tracked brand, top_sources the assistants cite, competitors, questions and engines.
GET /sites/{site}/findings?status=fail,warn
Checks from the latest scan with evidence and the fix (summary, where, ready-to-paste code when there is one). Without status all checks are returned.
GET /sites/{site}/answers
Stored AI answers, newest first. Each answer has the question, the assistant (engine), whether your brand was named and cited, its rank, sentiment, every tracked brand in the answer and the sources it cited.
Parameters: engine, question_id, since (ISO date), limit (1–500, default 100), cursor, include_answer=true for the full answer text. When next_cursor is not null, pass it as cursor to get the next page.
GET /sites/{site}/history
Score after each scan, oldest first (limit up to 200).
GET /sites/{site}/trends
Time series for charts and reports, grouped by day, week (Monday) or month (UTC). Parameters: from and to (YYYY-MM-DD, inclusive; default the last 90 days, up to two years), bucket (default depends on the range). The response has periods (period start dates), series (one array per metric, aligned with periods, null where there is no data), a catalog describing each series (unit: score, pct, count or rank; better: up or down) and the changes in the range.
Series: score.overall, score.seo, score.geo, score.aeo, score.aio (last scan in each period), issues.fail, issues.warn, ai.visibility, ai.cited, ai.positive, ai.negative (percentages), ai.position (average rank when named), ai.answers, engine.<assistant> and intent.<intent> (visibility %), and brand.<competitor domain> (share of answers naming that competitor).
GET /sites/{site}/changes
What changed between scans: new issues, things that got worse, and better (fixed). Parameters: since, limit.
Errors
Errors use HTTP status codes and a JSON body: { "error": "NOT_FOUND", "message": "…" }.
401missing, invalid or revoked key403 API_NOT_IN_PLANthe workspace is not on the Agency plan404the site is not in this workspace405the API is read-only429rate limited
Webhooks
Add an https address in Settings and choose the events to receive. Each event is sent as a POST with a JSON body:
{
"id": "evt_4Hh2…",
"type": "scan.completed",
"api_version": "v1",
"created_at": "2026-10-05T10:00:00.000Z",
"data": {
"site": { "id": "k3Jd9x2Lq", "domain": "example.com", "app_url": "…" },
"audit_id": "…",
"checked_at": "2026-10-05T10:00:00.000Z",
"score": { "overall": 92, "seo": 95, "geo": 88, "aeo": 90, "aio": 94 },
"issues": { "critical": 0, "warnings": 2 },
"changes": [{ "finding_id": "seo-title", "kind": "worse", "from": "pass", "to": "warn", "title": "Title tag is too long", "at": "…" }],
"report_url": "https://olabassist.com/r/…"
}
}
Events
score.dropped: the overall score fell by at least the webhook's threshold (10% by default, 1–90% per webhook). Previous and new score, the drop in points and percent, and every category's change.visibility.lost: an AI assistant named your brand in its last answer to a tracked question and does not any more. Includes the assistant, the question andnamed_instead, the competitors it recommends instead.visibility.gained: an assistant names your brand now and did not before, with its rank.scan.completed: a scan finished (weekly, manual or a re-check). Scores, issue counts and what changed.answers.updated: new AI answers for one tracked question, one entry per assistant, in the same format as/answers.site.createdandsite.deleted.
Use Send test in Settings to receive a ping event.
Slack, Discord, Asana, ClickUp
Each webhook has a message format. Paste a Slack or Discord incoming webhook address and OLAB Assist sends a readable one-line alert with a link, in the account's language (Slack and Discord addresses are detected automatically). For Asana, ClickUp, Trello, HubSpot and others, use the JSON format with Zapier, Make or n8n: a "Catch webhook" trigger followed by "Create task" turns every score.dropped or visibility.lost into a task. Every delivery carries OLAB-Signature; Slack and Discord simply ignore it.
Verifying the signature
Every request carries OLAB-Event, OLAB-Delivery and OLAB-Signature: t=<unix seconds>,v1=<hex>. The signature is an HMAC-SHA256 of t + "." + raw body with your webhook's signing secret. Compare it in constant time and reject old timestamps:
// Node.js
import crypto from 'node:crypto';
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
# Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
Retries
Answer with any 2xx status within 10 seconds. Anything else is retried after about 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours. After 25 failed deliveries in a row the webhook is paused; turn it back on in Settings. Use id to ignore an event you already processed.
MCP server
The MCP endpoint is https://api.olabassist.com/mcp (Streamable HTTP). It exposes the API endpoints as read-only tools: list_sites, get_site, get_findings, get_ai_answers, get_score_history, get_trends, get_changes.
Clients that accept headers (Claude Code, Cursor and others):
{
"mcpServers": {
"olab-assist": {
"url": "https://api.olabassist.com/mcp",
"headers": { "Authorization": "Bearer olab_YOUR_KEY" }
}
}
}
Claude Code: claude mcp add --transport http olab-assist https://api.olabassist.com/mcp --header "Authorization: Bearer olab_YOUR_KEY"
Clients that only take a URL can use https://api.olabassist.com/mcp/olab_YOUR_KEY. Treat that URL like a password, and use a separate key for it so you can revoke it on its own.
Example questions: “Which of my clients' sites lost points this week?”, “Who does ChatGPT recommend instead of example.com?”, “List the critical issues on example.com with the fix for each.”
Looker Studio (Data Studio)
The OLAB Assist connector for Looker Studio reads the same v1 API. On the Integrations page create a key, choose Connect in Looker Studio, allow access and paste the key. Data is read when the report is opened and kept for the data source's data freshness period (12 hours by default; choose 15 minutes or 1 hour for quicker updates). Google renamed Looker Studio to Data Studio in 2026; it is the same product.
Pick one table per data source and filter by one site or all sites. Combine data sources in a report with the Site field. Each table opens with a sensible default chart.
- Score history (default: GEO score over time): one row per scan with Scan date, Site, Report and all scores and issue counts. Follows the report's date range.
- AI visibility vs competitors (default: times named by brand): one row per tracked brand in each AI answer, with Asked on, Site, Brand, Own brand, Assistant, Question, Intent and the Times named, Times cited and Rank metrics. Chart it over time by Brand to compare with competitors.
- Issue trend by category (default: critical issues over time): one row per scan and category (SEO, GEO, AEO, AIO) with Critical issues, Warnings, Open issues and the category score.
- Sites: Site, Brand name, Last checked, Report, Score, SEO/GEO/AEO/AIO score, Critical issues, Warnings, AI answers checked, AI answers naming the brand, AI named rate, AI rank among tracked brands.
- Open issues: Site, Issue, Status, Category, Impact, Evidence, How to fix, Where, Check ID and an Issues count.
- AI answers: one row per answer with Asked on, Site, Question, Intent, Assistant, Named, Cited as source, Sentiment, Brands named and the Answers, Answers naming the brand, Answers citing the site and Rank when named metrics. Follows the report's date range.
A useful starting point: a time series of Score from Score history, and a table of Answers naming the brand ÷ Answers by Assistant from AI answers.
Notion
No code needed. On the Integrations page choose Connect Notion. On Notion's approval screen either duplicate the AI Visibility Dashboard template (one click, nothing else to choose) or share a page, then pick it. You get a branded dashboard page with a summary per site and three databases shown inline, kept current after every scan and every new AI answer (at most about an hour later, or right away with Sync now):
- Sites: one row per site with the overall and SEO, GEO, AEO, AIO scores, critical issues and warnings, how often AI answers name you, your rank among tracked brands, dates and links to the report and dashboard.
- Issues: open findings with status, category, impact, evidence and how to fix them. Fixed issues are marked Resolved instead of deleted.
- AI answers (the weekly AI prompt log): one row per answer from the last 30 days onward, with the question, its intent, assistant, whether you were named, rank, citation, sentiment, the brands named and the sources.
Rows are updated in place, never duplicated. You can add your own columns and views; our columns are overwritten on each sync. New columns we add later appear in your existing databases automatically. Disconnecting stops updates and leaves the pages in Notion.
Intent and sentiment
Every tracked question gets an intent label: research, comparison, purchase or brand, set once by a small language model (with a keyword fallback). When an answer names your brand, its tone is labelled positive, neutral or negative. Both appear in the API (questions[].intent, answers[].question.intent, answers[].sentiment, ai_visibility.by_intent), in Notion and in Looker Studio, and the dashboard can filter answers by them.
Versioning
This is version 1. Within v1 we only add: new endpoints, new fields and new event types. We do not remove or rename fields, change their meaning or change types. Ignore fields you do not know and your integration keeps working as we add features. If a breaking change is ever needed it will ship as /api/v2 next to v1, and v1 will keep running for a transition period announced by email.
Changelog
- 2026-10: v1 released: REST API, webhooks (
scan.completed,answers.updated,site.created,site.deleted) the MCP server and the Notion sync. - 2026-10: alerts (
score.dropped,visibility.lost,visibility.gained), Slack and Discord formats, the Notion dashboard template, Looker Studio competitor and category tables, question intent.