[ CH_05.01 / REST_API ]
REST API reference
All endpoints live under /api/v1. Two auth modes: interactive/management endpoints use a session JWT (Authorization: Bearer <jwt>) from magic-link login, while the read-only Analytics API (/projects/{id}/analytics/*) and the agent recommendations feed authenticate with a per-project API key sent as the x-api-key header — generate one under your project's Settings → Developer & API → Agent API Key. Analytics calls are metered per project and return X-RateLimit-* headers.
To create and manage projects programmatically, use an account API key (vsak_) with the Provisioning API — it spans every project you own and carries explicit write scopes. The per-project vsk_ key stays read-only and is minted from a project that already exists, so it can neither create one nor reach a second.
> base_url
https://api.vectraseo.com/api/v1 This reference is generated from the API's own route table, so it lists every customer route (221 of them). Scope says what a caller needs:
- A scope name such as
projects:read: a signed-in session, or an account key (vsak_) that holds that scope. Two names joined by + means the key needs both. - project key: the
x-api-key header only, with the project's vsk_ key or an account key that holds projects:read. A session is not accepted. - session: a signed-in session only. No API key can call it.
- none: no authentication.
Plan shows paid when the route returns 403 with code: "subscription_limit" on a free account. paid* means only some requests are paid, as the description says. Plan quotas, such as the monthly post allowance, also apply and are not shown here.
Authentication
MethodPathScopePlanDescription
POST/auth/magic-linknoneRequest magic link
POST/auth/magic-link/verifynoneVerify token, get JWT
POST/auth/logoutnoneClear the session cookie
GET/auth/mesessionThe signed-in user, with admin, partner and billing status
PATCH/auth/preferencessessionSave UI preferences on your user
POST/auth/lookupnoneReturn whether an email has a registered passkey.
POST/auth/passkey/register/beginsessionStart registering a passkey (WebAuthn options)
POST/auth/passkey/register/completesessionFinish registering a passkey
POST/auth/passkey/authenticate/beginnoneStart a passkey sign-in (WebAuthn challenge)
POST/auth/passkey/authenticate/completenoneFinish a passkey sign-in
GET/auth/passkeyssessionList your passkeys
PATCH/auth/passkeys/:credential_idsessionRename a passkey
DELETE/auth/passkeys/:credential_idsessionDelete a passkey
API keys
MethodPathScopePlanDescription
GET/account/api-keys/scopesnoneEvery account-key scope and what it grants
GET/account/api-keyssessionYour account keys (metadata only, never the key)
POST/account/api-keyssessionCreate a vsak_ account key with chosen scopes. The key is returned once
DELETE/account/api-keys/:key_idsessionRevoke an account key, effective immediately
GET/projects/:id/api-keysessionThe project key's prefix and dates, never the key
POST/projects/:id/api-key/rotatesessionIssue a new vsk_ project key, returned once. The old key stops working
POST/projects/:id/api-key/revokesessionRevoke the project key. Later calls get 401
Provisioning API (account key)
MethodPathScopePlanDescription
GET/account/meprojects:readIdentify the calling key: account, plan, project count
GET/account/projectsprojects:readEvery project on the account
POST/onboarding/audit/:audit_idsessionTurn a completed free audit into a project with a site-health monitor
POST/onboardingprojects:writeCreate AND configure a project in one call → 202 {project_id, job_id}
GET/onboarding/:job_idprojects:readPoll an onboarding run: per-step outcomes + the project as it stands
Projects
MethodPathScopePlanDescription
GET/projectsprojects:readList projects
POST/projectsprojects:writeCreate project
GET/projects/dashboard-summarysessionPost and competitor counts per project, for the dashboard
GET/projects/list-summarysessionPer-project card data for the projects list
GET/projects/:idprojects:readGet project
PUT/projects/:idprojects:writeUpdate project
POST/projects/:id/url-notices/ackprojects:writeHide the "we fixed a typo in your URL" notice for one field.
DELETE/projects/:idsessionDelete project
GET/projects/:id/summaryprojects:readSchedule and post count for one project
POST/projects/:id/runjobs:writepaidTrigger pipeline run: competitor analysis, gaps, posts → 202 {job_id}
GET/projects/:id/generation-configprojects:readRead the project's intent-weighted topic-generation policy.
PUT/projects/:id/generation-configprojects:writeReplace the generation policy. Then call /gaps/rescore to re-rank existing gaps
POST/projects/:id/credentials/revealsessionReveal one stored CMS credential
GET/projects/:id/indexnowprojects:readThe project's IndexNow key, where to host it, and the last check.
POST/projects/:id/indexnow/checkprojects:writeMint the key if needed and check the key file on the project's host now.
Competitors
MethodPathScopePlanDescription
GET/projects/:id/competitorsprojects:readList competitors
GET/projects/:id/competitors/discoverprojects:readSuggest competitors from DataForSEO
POST/projects/:id/competitorsprojects:writeAdd competitor
DELETE/projects/:id/competitors/:cidprojects:writeRemove competitor
POST/projects/:id/competitors/analyzejobs:writepaidRun analysis (starts the same full content run as /run) → 202 {job_id}
Content Gaps
MethodPathScopePlanDescription
GET/projects/:id/gapsprojects:readList gaps (paginated: ?limit, ?cursor)
POST/projects/:id/gaps/identifyjobs:writepaidIdentify gaps → 202 {job_id}
POST/projects/:id/gaps/rescoreprojects:writeRe-rank existing gaps after a generation-config change. No new gaps, no model call
POST/projects/:id/gaps/:gap_id/rejectprojects:writeMark a topic off-target. Kept as a negative example for later gap identification
DELETE/projects/:id/gaps/:gap_idgaps:deleteDelete gap
Blog Posts
MethodPathScopePlanDescription
GET/projects/:id/postsprojects:readList posts (paginated: ?limit, ?cursor)
POST/projects/:id/posts/generatejobs:writeGenerate posts → 202 {job_id}
POST/projects/:id/posts/bulk-delete-archivedsessionPermanently delete up to 20 archived posts, preserving request order.
POST/projects/:id/images/presignsessionGet a presigned URL to upload an image
POST/projects/:id/posts/hybrid-generatejobs:writeGenerate a post from your own outline, with an optional image
POST/projects/:id/posts/title-recommendationssessionpaidRecommend SEO/conversion titles and focus keywords for outline-based posts.
GET/projects/:id/posts/:post_idprojects:readGet post
GET/projects/:id/posts/:post_id/index-stateprojects:readCached Google URL Inspection state for the post's live URL
POST/projects/:id/posts/:post_id/recheck-indexsessionRe-run URL Inspection for the post's live URL
GET/projects/:id/posts/:post_id/gscprojects:readSearch Console metrics for one post
GET/projects/:id/posts/:post_id/bodyprojects:readThe post's HTML body
PUT/projects/:id/posts/:post_idprojects:writeUpdate post
POST/projects/:id/posts/:post_id/repair-metadatasessionpaidAI repair of the post's SEO title and meta description
DELETE/projects/:id/posts/:post_idsessionDelete post
POST/projects/:id/posts/:post_id/publishcontent:publishPublish to CMS
GET/projects/:id/posts/:post_id/exportsessionExport a post as an HTML or Markdown file download.
POST/projects/:id/posts/:post_id/regenerate-imagessessionpaidRegenerate the post's images → 202 {job_id}
POST/projects/:id/posts/:post_id/repair-contentjobs:writepaidRun Auto-fix on the post's content (queued job)
POST/projects/:id/posts/:post_id/regenerate-contentjobs:writepaidRewrite the post without the product claims its destination rejected, then re-check it
POST/projects/:id/posts/:post_id/advance-phasesessionpaidRun the next publish-readiness step on the post
POST/projects/:id/posts/:post_id/replaysessionpaidSend an archived post back through publish readiness
POST/projects/:id/posts/:post_id/remove-unconfirmed-claimssessionpaidRemove unconfirmed claims from a held draft and re-check it
POST/projects/:id/posts/:post_id/archivesessionArchive a post. Archived posts are purged after five days
POST/projects/:id/posts/:post_id/unarchivesessionRestore an archived post.
POST/projects/:id/posts/:post_id/retry-truthsessionpaidRe-run the claim check on the post
Scheduler
MethodPathScopePlanDescription
GET/projects/:id/scheduleprojects:readGet schedule
PUT/projects/:id/scheduleprojects:writeUpdate schedule
DELETE/projects/:id/scheduleprojects:writeDisable schedule
Sitemap
MethodPathScopePlanDescription
POST/projects/:id/sitemapjobs:writeFetch the project's sitemap → 202 {job_id}
GET/projects/:id/sitemapprojects:readStored sitemap data
DELETE/projects/:id/sitemapsessionClear stored sitemap data
Monitors
MethodPathScopePlanDescription
GET/projects/:id/monitorsprojects:readList monitors
GET/projects/:id/monitors/summaryprojects:readSite-monitor rollup for the project
POST/projects/:id/monitorsprojects:writeCreate monitor
GET/projects/:id/monitors/:midprojects:readGet monitor
PATCH/projects/:id/monitors/:midprojects:writeUpdate monitor (partial)
DELETE/projects/:id/monitors/:midsessionDelete monitor
POST/projects/:id/monitors/:mid/scanjobs:writeTrigger manual scan
POST/projects/:id/monitors/:mid/refresh-sitemapjobs:writeRe-fetch the monitor's sitemap (queued job)
GET/projects/:id/monitors/:mid/scansprojects:readList scan history
GET/projects/:id/monitors/:mid/scans/:sid/issuesprojects:readA scan's issues — bare JSON array; next page via the X-Next-Cursor header (?cursor=)
POST/projects/:id/monitors/:mid/issues/implementjobs:writepaid*Fix one issue on a page VectraSEO published → 202. Model fixers need a paid plan; deterministic ones are free
POST/projects/:id/monitors/:mid/issues/implement-batchjobs:writepaid*Fix every VectraSEO-published page one rule flagged in a scan. Model fixers need a paid plan
POST/projects/:id/monitors/:mid/issues/change-setjobs:writepaid*Build a reviewable change-set for a page VectraSEO cannot republish → 202 {job_id}. Model fixers need a paid plan
GET/projects/:id/change-setsprojects:readThe project's stored change-sets
GET/projects/:id/change-sets/job/:job_idprojects:readThe change-set a finished change-set job built
POST/projects/:id/monitors/:mid/remediation/snapshotssessionRecord that you copied or downloaded a scan's fix plan
GET/projects/:id/monitors/:mid/remediation/snapshotssessionFix-plan export history, newest first
GET/projects/:id/monitors/:mid/remediationsessionStatus of each finding in the current fix plan, plus verified fixes
MethodPathScopePlanDescription
GET/projects/:id/visibility/monitorsprojects:readList AI visibility monitors
GET/projects/:id/visibility/summaryprojects:readPer-project rollup
POST/projects/:id/visibility/monitorsprojects:writepaidCreate AI visibility monitor
GET/projects/:id/visibility/monitors/:midprojects:readGet monitor (with prompts)
GET/projects/:id/visibility/monitors/:mid/remediationprojects:readRemediation plan from the latest completed scan
PATCH/projects/:id/visibility/monitors/:midprojects:writeUpdate monitor — assistants, prompts, schedule, alerts
DELETE/projects/:id/visibility/monitors/:midprojects:writeDelete monitor
POST/projects/:id/visibility/monitors/:mid/scanjobs:writepaidTrigger manual citation scan
GET/projects/:id/visibility/monitors/:mid/scansprojects:readList scan history
GET/projects/:id/visibility/monitors/:mid/scans/:sid/resultsprojects:readGet scan results per assistant
GET/capabilities/visibility-assistantsnoneWhich AI assistants visibility scans can query
MethodPathScopePlanDescription
GET/projects/:id/ranksprojects:readList tracked keywords + current rank + 30-day history
GET/projects/:id/ranks/keywordsprojects:readList tracked keywords
POST/projects/:id/ranks/keywordsprojects:writeAdd a tracked keyword
DELETE/projects/:id/ranks/keywords/:keywordprojects:writeRemove a tracked keyword
GET/projects/:id/ranks/summaryprojects:readTop wins this week + drop alerts rollup
GET/projects/:id/ranks/suggestionsprojects:readAuto-suggest keywords from project + GSC
POST/projects/:id/ranks/fetchjobs:writeForce a rank refresh for the project
Google Search Console
MethodPathScopePlanDescription
GET/projects/:id/gsc/statussessionSearch Console connection status
GET/projects/:id/gsc/summarysessionProject-level clicks and impressions
GET/projects/:id/gsc/pagessessionSearch Console data for sitemap pages that are not posts
POST/projects/:id/gsc/authorizesessionStart OAuth — returns Google consent URL
POST/projects/:id/gsc/propertiessessionExchange the OAuth code and list your Search Console properties
POST/projects/:id/gsc/connectsessionBind a Search Console property to the project
DELETE/projects/:id/gsc/connectsessionDisconnect Search Console (stored metrics are kept)
POST/projects/:id/gsc/refreshsessionForce a Search Console refresh (queued job)
POST/projects/:id/gsc/sitemapsessionSubmit this project's sitemap to Google now.
GET/projects/:id/search/overviewprojects:readClicks, impressions, CTR, position (28d)
GET/projects/:id/search/queriesprojects:readTop queries
GET/projects/:id/search/pagesprojects:readTop pages
GET/projects/:id/search/pages/trendsprojects:readPer-page momentum — which pages are improving vs slipping over time.
GET/projects/:id/search/pages/historyprojects:readWeekly series for one page, with its current trend
GET/projects/:id/search/index-coverageprojects:readPage indexing buckets from URL Inspection API
GET/projects/:id/indexing-healthprojects:readDiscovery-health findings for the project's published posts.
GET/projects/:id/search/index-coverage/issuesprojects:readFAIL-bucket URLs with example fixes
GET/projects/:id/search/index-coverage/urlsprojects:readInspected URLs, filterable and cursor-paginated
POST/projects/:id/search/index-coverage/inspect-urljobs:writeRe-run URL Inspection on one URL now ("Validate fix")
Google Indexing API
MethodPathScopePlanDescription
GET/projects/:id/indexing-api/statusprojects:readConnection state (never returns the secret)
PUT/projects/:id/indexing-api/credentialssessionSave your own Google OAuth client and record your risk consent
POST/projects/:id/indexing-api/authorizesessionStart OAuth with your own client — returns the consent URL
POST/projects/:id/indexing-api/connectsessionExchange the OAuth code and turn on notifications
PUT/projects/:id/indexing-api/enabledprojects:writePause or resume notifications, keeping the connection
DELETE/projects/:id/indexing-apisessionRemove the credentials and the connection
POST/projects/:id/indexing-api/request-indexingjobs:writeSubmit selected URLs to the Google Indexing API (URL_UPDATED)
POST/projects/:id/indexing-api/request-all-not-indexedjobs:writeSubmit every not-indexed page in one action
GA4
MethodPathScopePlanDescription
GET/projects/:id/ga4/statussessionConnection status + bound property
POST/projects/:id/ga4/authorizesessionStart OAuth — returns Google consent URL
POST/projects/:id/ga4/propertiessessionList connectable GA4 properties for the account
POST/projects/:id/ga4/connectsessionBind a GA4 property to the project
DELETE/projects/:id/ga4/connectsessionDisconnect GA4
POST/projects/:id/ga4/refreshsessionForce a GA4 metrics refresh
GET/projects/:id/ga4/pagessessionGA4 top pages + next-best-actions
First-party traffic (push your own logs)
MethodPathScopePlanDescription
POST/projects/:id/traffic/dailytraffic:writepaidPush one UTC day of per-page aggregates. A replay replaces that day
GET/projects/:id/traffic/overviewprojects:read28-day totals vs the previous 28, daily series, top referrers, AI crawlers, countries, UTM
GET/projects/:id/traffic/pagesprojects:readPer-page human visits, crawler fetches by class, AI-assistant referrals
Recommendations
MethodPathScopePlanDescription
GET/projects/:id/recommendationsrecommendations:readList active recommendations across monitors
GET/projects/:id/recommendations/:rule_id/:target_kind/:target_idjobs:write + recommendations:readpaidPer-rule detail (cache-or-generate)
POST/projects/:id/recommendations/:rule_id/:target_kind/:target_id/regeneratejobs:write + recommendations:readpaidForce a fresh AI generation
POST/projects/:id/recommendations/:rule_id/:target_kind/:target_id/optimizedprojects:write + recommendations:readDismiss until its Search Console numbers change
POST/projects/:id/recommendations/:rule_id/:target_kind/:target_id/applycontent:publish + recommendations:readApply the suggested fix (republishes the post)
POST/projects/:id/recommendations/apply-allcontent:publish + recommendations:readApply every applicable fix in one job. Send dry_run: true first to preview
GET/projects/:id/agent/recommendationsproject keyAgent-consumable feed (GSC index FAIL buckets, monitor issues, AEO gaps) — see below
Analytics API (x-api-key · read-only · paid plans)
MethodPathScopePlanDescription
GET/projects/:id/analytics/overviewproject keypaidOne-call snapshot: health (AEO score nested), visibility, search, traffic, ranks; each surface nullable (?period_days)
GET/projects/:id/analytics/search/overviewproject keypaidGSC clicks, impressions, CTR, position + daily series
GET/projects/:id/analytics/search/queriesproject keypaidTop search queries (?limit)
GET/projects/:id/analytics/search/pagesproject keypaidPer-URL performance (?sort, ?limit)
GET/projects/:id/analytics/search/index-coverageproject keypaidGoogle index buckets + sparklines
GET/projects/:id/analytics/traffic/pagesproject keypaidGA4 per-page sessions, revenue, AI referrals
GET/projects/:id/analytics/traffic/first-partyproject keypaidTraffic you push via /traffic/daily: 28-day per-page visits (?sort=human|human_delta|ai_crawler, ?limit)
GET/projects/:id/analytics/visibility/summaryproject keypaidPer-monitor AI Visibility rollup (score/citation rate/SoV under latest_scan)
GET/projects/:id/analytics/visibility/monitors/:mid/scansproject keypaidAI Visibility scan history, newest first — bare array (?limit)
GET/projects/:id/analytics/visibility/monitors/:mid/scans/:sid/resultsproject keypaidPer-prompt, per-assistant results
GET/projects/:id/analytics/health/summaryproject keypaidSite-health + AEO score, issue counts
GET/projects/:id/analytics/health/monitors/:mid/scansproject keypaidSite-health scan history, newest first — { items, count } (?limit)
GET/projects/:id/analytics/health/monitors/:mid/scans/:sid/issuesproject keypaidIssues for a scan (severity, category, fix)
GET/projects/:id/analytics/ranksproject keypaidTracked keywords + position history (?days)
Verification Receipt (public)
MethodPathScopePlanDescription
GET/verification/:project_id/:post_idnonePublic sanitized truth report for a published post. No auth — gated to status=published.
Automation Status
MethodPathScopePlanDescription
GET/projects/:id/automationsessionRead-only rollup of generation, publish, monitor, and rank automation tracks
Jobs
MethodPathScopePlanDescription
GET/jobs/:job_idprojects:readPoll job status
POST/jobs/:job_id/canceljobs:writeRequest cancellation of a queued or running job
GET/jobs/:project_id/jobsprojects:readList a project's jobs
Billing
MethodPathScopePlanDescription
POST/billing/checkoutsessionStart a Stripe Checkout session, or switch plan if already subscribed
POST/billing/switchsessionSwitch an existing subscription to a different plan.
POST/billing/portalsessionOpen the Stripe customer portal
GET/billing/subscriptionsessionGet current user's subscription status for a product (content|local).
GET/billing/plansessionYour plan limits, per product
POST/billing/cancelsessionCancel subscription at period end.
POST/billing/resumesessionUndo a pending cancellation.
GET/billing/local-scout/refund-eligibilitysessionWhether the user's Local Scout subscription is within the money-back window.
POST/billing/local-scout/refundsessionHonor the Local Scout 30-day money-back guarantee: refund the latest payment and cancel the subscription immediately.
POST/billing/verify-sessionsessionVerify a completed checkout session and sync subscription.
GET/billing/confignoneStripe publishable key
Free tools and public endpoints
MethodPathScopePlanDescription
POST/contactnoneSend a message to VectraSEO
POST/auditnoneStart a free site audit → 202 {audit_id}
GET/audit/:audit_idnonePoll a free audit
POST/audit/:audit_id/emailnoneSubmit an email to unlock the full audit report
GET/audit/:audit_id/og.pngnonePer-scan Open Graph share image.
GET/public/stats/smb-seononeLatest aggregate small-business SEO statistics
GET/public/stats/partner-countnoneNumber of approved partners
POST/public-tools/ai-visibilitynoneRun the AEO readiness checks on one public URL
POST/public-tools/ai-visibility-livesessionpaidQueue a live AI-assistant visibility scan
GET/public-tools/ai-visibility-live/:sidnonePoll a live visibility scan
POST/public-tools/local-scoutsessionpaidQueue a Local Scout scan (needs a Local Scout plan)
GET/public-tools/local-scout/scans/:sidsessionPoll a Local Scout scan
POST/public-tools/local-scout/previewnoneInstant local-findability snapshot
POST/public-tools/local-scout/unlocknoneSubmit an email to see the full Local Scout fix list
GET/public-tools/local-scout/unsubscribenoneUnsubscribe from Local Scout follow-up emails
POST/public-tools/robots-testnoneTest robots.txt rules, fetched from a URL or pasted
POST/public-tools/meta-descriptionsessionpaidGenerate meta description options
POST/public-tools/title-tagsessionpaidGenerate title tag options
Partner program
MethodPathScopePlanDescription
POST/affiliates/applynoneApply to the partner program
POST/affiliates/validate-refnoneCheck whether a referral code belongs to an approved partner
POST/affiliates/track-clicknoneRecord a click on a partner link
GET/affiliates/programnoneProgram terms: commission rate, window, eligible plans
GET/affiliates/applications/:application_idnoneRead back a submitted application
GET/affiliates/mesessionYour partner profile
GET/affiliates/dashboardsessionPartner dashboard totals
GET/affiliates/commissionssessionYour commission ledger (paginated)
GET/affiliates/referralssessionCustomers you referred (paginated)
GET/affiliates/clicks/dailysessionDaily click counts
PATCH/affiliates/settingssessionUpdate your partner settings
GET/affiliates/assetssessionMarketing assets with your referral link filled in
Agent recommendations feed
GET /projects/:id/agent/recommendations returns every current recommendation for a project in one call: Search Console findings (including Page Indexing failures), site-health SEO issues and AEO issues. It takes a project key, works on every plan, and is read-only.
severity_min
Lowest severity to include: critical, warning, info, opportunity or positive. Default positive, which keeps everything.
categories
Comma-separated subset of gsc, seo, aeo. Default all three.
limit_per_rule
Cap on items per rule, 0–500. Default 0, which means no cap.
format
json (default) or markdown.
With format=markdown the response is text/markdown: the same items written as a briefing you can paste into a coding agent such as Claude Code or Cursor. To have Claude Code fetch it and act on it:
claude-code.sh
claude "Pull SEO recommendations from
https://api.vectraseo.com/api/v1/projects/$PROJECT_ID/agent/recommendations?format=markdown
using header x-api-key: <your-key>, then group fixes by rule_id
and open a PR."
For Cursor or any other agent, fetch the briefing and paste it into the chat:
fetch-briefing.sh
curl -H "x-api-key: <your-key>" \
"https://api.vectraseo.com/api/v1/projects/$PROJECT_ID/agent/recommendations?format=markdown&severity_min=warning"
Each JSON item has a stable id, so you can compare runs to see what is new and what is resolved. A missing, revoked or wrong key returns 401 invalid_api_key; a query value outside the allowed set returns 422.
Rate limits
There is one hourly limit, and it is the same on every plan: 1,000 requests per hour (the analytics_api_rate_limit_per_hour setting). It is counted in two buckets:
- Per project for routes whose scope is project key (the Analytics API and the agent feed), whichever key you send.
- Per account for an account key (
vsak_) on every other key-reachable route.
Metered responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Over the limit the API returns 429 with Retry-After: 3600. Calls made with a signed-in session are not counted.
Analytics API · response shapes
Each /analytics/* response below is the exact shape the live API returns. Three conventions trip up first-time integrators:
- AEO readiness lives inside
health. The overview reports it as health.aeo_score — there is no top-level aeo object. - Surfaces can be
null. In /overview, each of health, visibility, search, traffic, and ranks is null until that source is connected and has data — normalize defensively. - Only scan issues are cursor-paginated.
/analytics/health/monitors/:mid/scans/:sid/issues returns { items, count, next_cursor } — follow next_cursor until it is null. Every other list endpoint returns { items, count } in one response (visibility scans and visibility results return a bare array), with no next_cursor/total. None has server-side severity/category filters. - Get a
scan_id from the scan lists./analytics/health/monitors/:mid/scans and /analytics/visibility/monitors/:mid/scans list a monitor's scans newest first (?limit 1–100, default 20). Each scan carries the scan_id the issues and results routes take; /analytics/health/summary and /analytics/visibility/summary give the monitor ids.
GET/projects/:id/analytics/overview
One-call snapshot. AEO is health.aeo_score (no top-level aeo). Every surface is null until its source is connected.
overview.json
{
"project_id": "prj_8fa21c",
"period_days": 28,
"health": { "health_score": 92, "aeo_score": 78, "urls_checked": 184,
"critical": 0, "warning": 3, "info": 9 },
"visibility": { "visibility_score": 61, "citation_rate": 0.34, "share_of_voice": 0.22 },
"search": { "clicks": 12431, "clicks_prev": 11705, "impressions": 402118,
"ctr": 0.031, "position": 14.2 },
"traffic": { "sessions": 18904, "engaged_sessions": 12880,
"key_events": 311, "revenue": 4820.50, "ai_referrals": 842 },
"ranks": { "tracked": 48, "top_10": 12, "wins_this_week": 3 },
"generated_at": "2026-05-29T14:02:11Z"
}GET/projects/:id/analytics/search/overview
GSC clicks, impressions, CTR, position. The daily time series is the daily field. connected is false until Search Console is linked.
search-overview.json
{
"connected": true,
"property_url": "https://acme.com/",
"period_days": 28,
"clicks": 12431, "impressions": 402118, "ctr": 0.031, "position": 14.2,
"clicks_prev": 11705, "impressions_prev": 389220, "ctr_prev": 0.030, "position_prev": 15.1,
"daily": [
{ "date": "2026-05-01", "clicks": 402, "impressions": 13200, "ctr": 0.030, "position": 14.8 }
]
}GET/projects/:id/analytics/search/queries
Top queries for the most recent refresh. Capped by ?limit (default 25, max 100). Not cursor-paginated.
search-queries.json
{
"items": [
{ "query": "ai seo audit tool", "clicks": 312, "impressions": 8400, "ctr": 0.037, "position": 6.1 }
],
"count": 25
}GET/projects/:id/analytics/search/pages
Per-URL performance with previous-period values and deltas. ?sort = clicks | clicks_delta | clicks_loss | position_delta | position_loss; ?limit max 200.
search-pages.json
{
"items": [
{
"kind": "post", "target_id": "post_4f2a",
"url": "https://acme.com/blog/aeo-guide", "title": "The AEO Guide",
"clicks": 312, "impressions": 8400, "ctr": 0.037, "position": 6.1,
"clicks_prev": 280, "impressions_prev": 8010, "ctr_prev": 0.035, "position_prev": 7.2,
"clicks_delta": 32, "position_delta": 1.1
}
],
"count": 50
}GET/projects/:id/analytics/search/index-coverage
Google index buckets + 30-day sparklines. severity is info | warning | critical | neutral — only indexed (live on Google) is info.
index-coverage.json
{
"connected": true,
"property_url": "https://acme.com/",
"buckets": [
{ "bucket": "indexed", "severity": "info", "count": 184, "sparkline": [180,181,184], "examples": [] },
{ "bucket": "crawled_not_indexed", "severity": "warning", "count": 12, "sparkline": [10,11,12], "examples": [] },
{ "bucket": "excluded_noindex", "severity": "warning", "count": 31, "sparkline": [31,31,31], "examples": [] }
],
"total_inspected": 227,
"rollup_days": 30,
"quota": { "used_today": 14, "daily_cap": 2000 },
"last_synced_at": "2026-05-29T06:00:00Z"
}GET/projects/:id/analytics/traffic/pages
Per-page GA4 metrics with previous-period values + ai_referrals (ChatGPT / Claude / Gemini). Returns { items, count } — not cursor-paginated.
traffic-pages.json
{
"items": [
{
"target_id": "post_4f2a",
"url": "https://acme.com/blog/aeo-guide", "path": "/blog/aeo-guide",
"matched": true,
"sessions": 2104, "engaged_sessions": 1502, "engagement_rate": 0.714,
"key_events": 41, "revenue": 980.00,
"sessions_prev": 1788, "engaged_sessions_prev": 1290, "engagement_rate_prev": 0.702,
"key_events_prev": 33, "revenue_prev": 740.00,
"ai_referrals": 156, "ai_referrals_prev": 92,
"source_counts": { "google": 1402, "chatgpt.com": 98, "claude.ai": 41 }
}
],
"count": 37
}GET/projects/:id/analytics/visibility/summary
Per-monitor AI Visibility rollup. Headline metrics live under each monitor latest_scan; best_score/worst_score summarise across monitors. No top-level score, no by_assistant.
visibility-summary.json
{
"has_monitor": true,
"best_score": 61,
"worst_score": 54,
"monitors": [
{
"monitor_id": "vis_2a91", "name": "Brand prompts", "enabled": true,
"schedule": "weekly", "assistants": ["chatgpt", "claude", "gemini"],
"last_scan_at": "2026-05-27T09:00:00Z", "next_scan_at": "2026-06-03T09:00:00Z",
"latest_scan": {
"scan_id": "vscan_88c1", "visibility_score": 61,
"citation_rate": 0.34, "share_of_voice": 0.22, "accuracy_flags": 2
}
}
]
}GET/projects/:id/analytics/visibility/monitors/:mid/scans/:sid/results
Per-prompt, per-assistant results. Returns a plain JSON array (no items envelope). response_accurate is reserved and currently always null.
visibility-results.json
[
{
"scan_id": "vscan_88c1", "created_at": "2026-05-27T09:01:12Z",
"assistant": "chatgpt", "prompt_id": "pr_01",
"prompt_text": "best ai seo tools 2026",
"answer_text": "…",
"cited": true, "text_cited": true, "domain_cited": true,
"position": 2, "matched_alias": "VectraSEO",
"matched_source": { "url": "https://acme.com/blog/aeo-guide" },
"sources": ["https://acme.com/blog/aeo-guide"],
"competitors_cited": ["arvow.com"], "response_accurate": null,
"model": "gpt-…", "cost_usd": 0.0021, "error": null
}
]GET/projects/:id/analytics/health/summary
Per-monitor site-health rollup. health_score, aeo_score, and issue counts live under each monitor latest_scan. No monitor → { has_monitor: false, monitors: [] }.
health-summary.json
{
"has_monitor": true,
"monitors": [
{
"monitor_id": "mon_3c1", "domain": "acme.com", "enabled": true,
"last_scan_at": "2026-05-29T05:00:00Z", "next_scan_at": "2026-05-30T05:00:00Z",
"latest_scan": {
"scan_id": "scan_5b2", "finished_at": "2026-05-29T05:03:41Z",
"urls_checked": 184, "health_score": 92,
"issue_counts": { "critical": 0, "warning": 3, "info": 9 },
"aeo_score": 78,
"aeo_issue_counts": { "critical": 1, "warning": 4, "info": 6 }
}
}
]
}GET/projects/:id/analytics/health/monitors/:mid/scans/:sid/issues
Issues from a scan. Returns { items, count, next_cursor }. Cursor-paginated: follow next_cursor (pass back as ?cursor=) until null; ?limit= defaults to 2000. There are no severity/category filter params (filter client-side).
health-issues.json
{
"items": [
{
"scan_id": "scan_5b2", "rule_id": "missing_meta",
"severity": "warning", "category": "seo",
"url": "https://acme.com/pricing", "url_hash": "…",
"message": "Page is missing a meta description.",
"recommendation": "Add a 140-160 char description summarising the page.",
"evidence": {}
}
],
"count": 47,
"next_cursor": null
}GET/projects/:id/analytics/ranks
Tracked keywords under items, each with a history array of { date, position, source }. top_wins_this_week carries the previous-position / jump deltas. ?days default 30, max 90.
ranks.json
{
"project_id": "prj_8fa21c",
"window_days": 30,
"fetched_at": "2026-05-29T06:00:00Z",
"schedule": { "cadence": "daily", "interval_hours": 24,
"last_run_at": "2026-05-29T06:00:00Z", "next_run_at": "2026-05-30T06:00:00Z" },
"items": [
{
"keyword": "answer engine optimization",
"current_position": 6, "last_fetched_date": "2026-05-29", "current_source": "gsc",
"history": [
{ "date": "2026-05-23", "position": 9, "source": "gsc" },
{ "date": "2026-05-29", "position": 6, "source": "gsc" }
]
}
],
"top_wins_this_week": [
{ "keyword": "answer engine optimization",
"current_position": 6, "previous_position": 9, "jump": 3 }
]
}