skip to content →
FIELD_MANUAL / LIVEREVISIONv1.2 · 2026.05.26Updated 2026-05-26CHAPTERS6 FILEDCH_05INTEGRATION
[ CH_05 / FIELD_MANUAL ]API · MONITORING · CMS_ADAPTERS

The field manual.
wire it in, read the signals.

VectraSEO's API and adapter library lets you generate SEO content, publish it to seven CMS platforms, and run continuous site-health monitoring against any sitemap. Authenticate with a JWT obtained via magic link, POST to /api/v1/projects/{id}/posts/generate or /api/v1/projects/{id}/posts/hybrid-generate to start a generation pipeline, then poll /api/v1/jobs/{id} until the job completes. Daily monitors run 42 SEO health rules plus 12 AEO readiness checks and email diff alerts when new issues appear.

[ NEW_TO_VECTRASEO? ] Configuring a project? Start with the Project Setup guide
[ 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 Project → Settings → Analytics API. 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
Authentication
POST/auth/magic-linkRequest magic link
POST/auth/magic-link/verifyVerify token, get JWT
Projects
GET/projectsList projects
POST/projectsCreate project
GET/projects/:idGet project
PUT/projects/:idUpdate project
DELETE/projects/:idDelete project
POST/projects/:id/runTrigger pipeline run
Competitors
GET/projects/:id/competitorsList competitors
GET/projects/:id/competitors/discoverSuggest competitors from DataForSEO
POST/projects/:id/competitorsAdd competitor
DELETE/projects/:id/competitors/:cidRemove competitor
POST/projects/:id/competitors/analyzeRun analysis
Content Gaps
GET/projects/:id/gaps?limit=20&cursor=List gaps (paginated)
POST/projects/:id/gaps/identifyIdentify gaps
DELETE/projects/:id/gaps/:gap_idDelete gap
Blog Posts
GET/projects/:id/posts?limit=20&cursor=List posts (paginated)
POST/projects/:id/posts/generateGenerate posts
GET/projects/:id/posts/:post_idGet post
PUT/projects/:id/posts/:post_idUpdate post
DELETE/projects/:id/posts/:post_idDelete post
POST/projects/:id/posts/:post_id/publishPublish to CMS
Blog Publish (Inbound)
POST/blog/publishCreate blog post
PUT/blog/publish/:idUpdate blog post
DELETE/blog/publish/:idDelete blog post
Scheduler
GET/projects/:id/scheduleGet schedule
PUT/projects/:id/scheduleUpdate schedule
DELETE/projects/:id/scheduleDisable schedule
Monitors
GET/projects/:id/monitorsList monitors
POST/projects/:id/monitorsCreate monitor
GET/projects/:id/monitors/:midGet monitor
PUT/projects/:id/monitors/:midUpdate monitor
DELETE/projects/:id/monitors/:midDelete monitor
POST/projects/:id/monitors/:mid/scanTrigger manual scan
GET/projects/:id/monitors/:mid/scansList scan history
GET/projects/:id/monitors/:mid/issuesList active issues
AI Visibility Monitor
GET/projects/:id/visibility/monitorsList AI visibility monitors
POST/projects/:id/visibility/monitorsCreate AI visibility monitor
GET/projects/:id/visibility/monitors/:midGet monitor (with prompts)
PATCH/projects/:id/visibility/monitors/:midUpdate monitor — assistants, prompts, schedule, alerts
DELETE/projects/:id/visibility/monitors/:midDelete monitor
POST/projects/:id/visibility/monitors/:mid/scanTrigger manual citation scan
GET/projects/:id/visibility/monitors/:mid/scansList scan history
GET/projects/:id/visibility/monitors/:mid/scans/:sid/resultsGet scan results per assistant
GET/projects/:id/visibility/summaryPer-project rollup
Keyword Rank Tracker
GET/projects/:id/ranksList tracked keywords + current rank + 30-day history
GET/projects/:id/ranks/keywordsList tracked keywords
POST/projects/:id/ranks/keywordsAdd a tracked keyword
DELETE/projects/:id/ranks/keywords/:keywordRemove a tracked keyword
GET/projects/:id/ranks/summaryTop wins this week + drop alerts rollup
GET/projects/:id/ranks/suggestionsAuto-suggest keywords from project + GSC
POST/projects/:id/ranks/fetchForce a rank refresh for the project
Google Search Console
GET/projects/:id/search/overviewClicks, impressions, CTR, position (28d)
GET/projects/:id/search/queriesTop queries
GET/projects/:id/search/pagesTop pages
GET/projects/:id/search/index-coveragePage indexing buckets from URL Inspection API
GET/projects/:id/search/index-coverage/issuesFAIL-bucket URLs with example fixes
GA4
GET/projects/:id/ga4/statusConnection status + bound property
POST/projects/:id/ga4/authorizeStart OAuth — returns Google consent URL
POST/projects/:id/ga4/propertiesList connectable GA4 properties for the account
POST/projects/:id/ga4/connectBind a GA4 property to the project
DELETE/projects/:id/ga4/connectDisconnect GA4
POST/projects/:id/ga4/refreshForce a GA4 metrics refresh
GET/projects/:id/ga4/pagesGA4 top pages + next-best-actions
Recommendations
GET/projects/:id/recommendationsList active recommendations across monitors
GET/projects/:id/recommendations/:rule_id/:target_kind/:target_idPer-rule detail (cache-or-generate)
POST/projects/:id/recommendations/:rule_id/:target_kind/:target_id/regenerateForce a fresh AI generation
POST/projects/:id/recommendations/:rule_id/:target_kind/:target_id/applyApply the suggested fix
GET/projects/:id/agent/recommendationsAgent-consumable feed (GSC index FAIL buckets, monitor issues, AEO gaps)
Provisioning API (x-api-key · account key · read + write)
GET/account/meIdentify the calling key: account, plan, project count
GET/account/projectsEvery project on the account
POST/onboardingCreate AND configure a project in one call → 202 {project_id, job_id}
GET/onboarding/:job_idPoll an onboarding run: per-step outcomes + the project as it stands
POST/projectsCreate a project (projects:write)
PUT/projects/:idEdit any project you own (projects:write)
POST/projects/:id/monitorsCreate a site-health monitor (projects:write)
PUT/projects/:id/scheduleSet the recurring content schedule (projects:write)
POST/projects/:id/runStart the content pipeline (jobs:write · spends budget)
POST/projects/:id/monitors/:mid/scanScan on demand (jobs:write)
POST/projects/:id/posts/:post_id/publishPublish to the connected CMS (content:publish · writes live)
GET/jobs/:job_idPoll any async job (projects:read)
Analytics API (x-api-key · read-only · paid plans)
GET/projects/:id/analytics/overviewOne-call snapshot: health (AEO score nested), visibility, search, traffic, ranks; each surface nullable (?period_days)
GET/projects/:id/analytics/search/overviewGSC clicks, impressions, CTR, position + daily series
GET/projects/:id/analytics/search/queriesTop search queries (?limit)
GET/projects/:id/analytics/search/pagesPer-URL performance (?sort, ?limit)
GET/projects/:id/analytics/search/index-coverageGoogle index buckets + sparklines
GET/projects/:id/analytics/traffic/pagesGA4 per-page sessions, revenue, AI referrals
GET/projects/:id/analytics/visibility/summaryPer-monitor AI Visibility rollup (score/citation rate/SoV under latest_scan)
GET/projects/:id/analytics/visibility/monitors/:mid/scans/:sid/resultsPer-prompt, per-assistant results
GET/projects/:id/analytics/health/summarySite-health + AEO score, issue counts
GET/projects/:id/analytics/health/monitors/:mid/scans/:sid/issuesIssues for a scan (severity, category, fix)
GET/projects/:id/analytics/ranksTracked keywords + position history (?days)
Verification Receipt (public)
GET/verification/:project_id/:post_idPublic sanitized truth report for a published post. No auth — gated to status=published.
Automation Status
GET/projects/:id/automationRead-only rollup of generation, publish, monitor, and rank automation tracks
Jobs
GET/jobs/:job_idPoll job status
GET/projects/:id/jobsList project jobs

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.
  • List endpoints are not cursor-paginated. They return { items, count } (visibility results return a bare array); there is no next_cursor/total and no server-side severity/category filters.
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 }
  ]
}
[ CH_05.02 / PROVISIONING_API ]

Create and manage projects programmatically

An account API key reaches every project you own and can create new ones. Everything the dashboard does to a project — short of deleting it — is reachable over REST, and over MCP for AI assistants.

Create a key under Settings → API keys, choosing the permissions the integration needs. The raw key is shown once — only its SHA-256 hash is stored, so it cannot be recovered afterwards. Keys are prefixed vsak_, which leaked-credential scanners recognise the way they do ghp_.

> confirm_your_key
curl -H "x-api-key: vsak_..." https://api.vectraseo.com/api/v1/account/me

Onboard a whole project in one call

POST /api/v1/onboarding takes an account from nothing to a configured, monitored, scheduled project: the project record, content brief, generation policy, competitors, site-health monitor, content schedule and Auto-Pilot policy — together.

onboard.sh
curl -X POST https://api.vectraseo.com/api/v1/onboarding \
  -H "x-api-key: vsak_..." -H "content-type: application/json" \
  -d '{
    "project": {
      "business_name": "Acme Robotics",
      "business_url": "https://acmerobotics.com",
      "industry": "Industrial automation",
      "target_audience": "Plant managers at mid-size manufacturers"
    },
    "competitors": ["https://competitor-a.com"],
    "monitor": { "schedule": "daily" },
    "schedule": { "interval_days": 7, "posts_per_run": 2 }
  }'

It answers 202 with a project_id you can use immediately and a job_id to poll at GET /api/v1/onboarding/{job_id}. It is asynchronous because creating a monitor fetches your sitemap, measured at 82.6s on a real sitemap index — well past the gateway's 29s ceiling. The project row itself is written synchronously, so a 202 means the project genuinely exists and a 4xx means nothing was created.

Every step is fault-isolated and reports its own outcome: a sitemap that 404s never costs you the schedule that would have been written after it. A run with failed steps still reports completed — re-apply just those steps with the individual endpoints. Re-running onboarding to retry would create a second project.

Nothing costs money unless you ask

analyze_competitors, run_initial_scan and start_content_run are the only paths that spend model budget or plan quota, and all three default to false. When you do set one, the plan check happens up front — you get a synchronous 403, not a surprise on the invoice.

Permissions

Scopes are chosen when a key is created and are fixed afterwards. Grant the least the integration needs; to change them, create a new key and revoke the old one.

SCOPE
GRANTS
projects:read
Read projects, posts, gaps, monitors, scans and jobs.
projects:write
Create projects and edit project settings and configuration.
jobs:write
Start content runs, site scans and auto-fixes. Consumes plan quota and model budget.
content:publish
Publish and republish posts to the connected CMS. Writes to your live website.

projects:read comes with any write scope, because every write path reads the project first to authorise it.

What no key can do

  • Delete a project. Deletion here is a real purge, not a reversible hide, so it stays a signed-in action in the dashboard. There is no scope for it.
  • Delete a monitor or a gap. The non-destructive equivalents are programmatic instead — disable the monitor, or reject the gap.
  • Manage API keys. Minting and revoking needs a session. Otherwise one leaked key could issue itself a wider one, and rotating the compromised key would not evict the attacker.
  • Reveal stored CMS credentials, touch billing, or reach another tenant. A project you do not own returns 404, so a key cannot probe for project ids.

Errors and limits

401 for an unknown, malformed or revoked key (the cases are not distinguished, so a caller cannot probe which applied). 403 names the missing scope, or carries code: "subscription_limit" when the plan is the blocker. 404 means the resource does not exist or is not yours. Quota is per account, in a fixed hourly window shared with the Analytics and Agent APIs, and every response carries X-RateLimit-*.

[ CH_05.03 / MCP_SERVER ]

Drive VectraSEO from an AI assistant

VectraSEO ships a Model Context Protocol server, so an assistant can create, onboard and manage projects directly. Same account key, same permissions as the Provisioning API.

> endpoint
https://api.vectraseo.com/mcp

Connect

claude-code.sh
claude mcp add --transport http vectraseo \
  https://api.vectraseo.com/mcp \
  --header "x-api-key: vsak_..."

Any client that speaks Streamable HTTP can point at that URL with an x-api-key (or Authorization: Bearer) header. For clients that can only launch a stdio subprocess, run npx -y @vectraseo/mcp with VECTRASEO_API_KEY set — a zero-dependency bridge to the same endpoint.

Tools

TOOL
DOES
whoami
Identify the key: account, plan, project count. Call this first.
list_projects
Every project on the account.
get_project
One project's full configuration.
onboard_project
Create AND fully configure a project in one call.
get_onboarding_status
Poll an onboarding run, step by step.
update_project
Edit any project — settings, brief, generation policy.
list_competitors
Competitors tracked for a project.
add_competitor
Attach a competitor URL.
list_monitors
A project's site-health monitors.
create_monitor
Create a monitor over a sitemap.
run_site_scan
Scan now instead of waiting for the schedule.
get_recommendations
Every current SEO/AEO/Search-Console fix, with the concrete change.
set_schedule
Recurring content schedule.
trigger_content_run
Run the full content pipeline. Spends budget.
get_job
Poll any asynchronous job.
list_posts
A project's blog posts, paginated.
publish_post
Publish or republish. Writes to the live site.

Safety

Tool calls run through the same REST API with the same credential, so an assistant can never do anything the API would refuse — the scope check, plan gate and ownership check all run in their usual place. Tools that spend budget or write to your live site are flagged destructiveHint so your client can prompt first, and the server tells the assistant to confirm with you before calling them. No tool can delete a project, and no tool can manage API keys — including its own, so a leaked key cannot perpetuate itself.

[ CH_05.04 / SITE_MONITORING ]

Continuous SEO health checks

Monitors crawl your sitemap on a schedule (or on demand), run 42 SEO health rules plus 12 AEO readiness checks against every URL, and roll results up into 0–100 SEO health and AEO readiness scores. When new critical or warning issues land on a scan, VectraSEO emails your configured recipients.

Creating a monitor

  1. 01Open your project and go to Monitors → New Monitor.
  2. 02Enter your sitemap URL (e.g. https://yoursite.com/sitemap.xml). If you only have a root URL, VectraSEO will try common sitemap locations.
  3. 03Pick a schedule: daily or weekly. An hourly EventBridge rule enqueues due monitors automatically.
  4. 04Optionally add email recipients for issue alerts — fires when new critical or warning issues appear on a scan.
  5. 05Hit Run Scan to kick off the first crawl manually. Scans cover up to 1,000 URLs and usually complete in 1–5 minutes.

The 54 checks

RULE_ID
SEVERITY
CHECKS
broken_status
CRITICAL
Flags any URL returning a 4xx or 5xx HTTP status code.
redirect_chain
WARNING
Flags URLs that redirect more than twice before hitting a final 200.
spa_placeholder
CRITICAL
Flags SPA routes that return placeholder HTML without the real content — bots see an empty page.
duplicate_initial_html
WARNING
Flags groups of URLs serving byte-identical initial HTML.
missing_meta
WARNING
Flags pages missing a title, meta description, or canonical link.
metadata_quality
WARNING
Flags duplicate, too-short, too-long, or weak title/description/H1 combinations.
canonical_quality
WARNING
Flags canonicals that are inconsistent, non-HTTPS, off-domain, redirected, or noindex.
og_twitter_tags
WARNING
Flags pages missing Open Graph or Twitter Card meta tags used for link previews.
heading_structure
WARNING
Flags pages with zero or more than one <h1> heading.
viewport_meta
WARNING
Flags pages missing <meta name="viewport"> for device-width mobile rendering.
image_alt
INFO
Flags <img> elements shipped without an alt attribute.
image_seo
INFO
Flags generic image filenames, missing dimensions, lazy LCP-image risk, and suspicious alt text.
noindex_in_sitemap
WARNING
Flags pages listed in the sitemap but marked noindex.
x_robots_tag
CRITICAL
Flags HTTP X-Robots-Tag headers that noindex or restrict search appearance.
robots_txt
CRITICAL
Flags robots.txt rules that block sitemap URLs, plus missing sitemap discovery hints.
sitemap_hygiene
WARNING
Flags sitemap entries that are non-HTTPS, redirected, parameterized, fragmented, or not final.
internal_links
WARNING
Flags internal links to broken, redirected, noindex, or non-final URLs in the scanned set.
hreflang
WARNING
Flags invalid, missing-self, unsuccessful, or non-reciprocal hreflang annotations.
mixed_content
WARNING
Flags HTTPS pages that load scripts, images, or stylesheets over HTTP.
slow_response
WARNING
Flags slow origin response times, with critical severity above the 3-second threshold.
performance_static
INFO
Flags static Core Web Vitals risk signals such as page weight, script count, and render blocking.
thin_content
INFO
Flags indexable pages with very little visible text content.
structured_data
WARNING
Flags missing, broken, or incomplete schema.org JSON-LD.
html_lang
WARNING
Flags pages missing a valid html lang attribute.
meta_refresh
WARNING
Flags meta refresh redirects that can confuse crawlers and users.
meta_viewport_scaling
WARNING
Flags viewport settings that disable user zoom or scaling.
link_text_quality
WARNING
Flags vague link labels such as "click here" that hide destination intent.
crawlable_anchors
WARNING
Flags links that are not crawlable anchors with usable href values.
doctype
INFO
Flags pages missing an HTML doctype declaration.
charset_declaration
INFO
Flags pages missing an early character-set declaration.
duplicate_ids
INFO
Flags duplicate element IDs that break labels, anchors, and assistive technology.
frame_title
INFO
Flags iframes without descriptive title attributes.
form_labels
INFO
Flags form controls without accessible labels.
button_link_name
INFO
Flags buttons and links without accessible names.
image_input_alt
INFO
Flags image inputs without alt text.
heading_order
INFO
Flags heading-level jumps that make document structure harder to parse.
tabindex_positive
INFO
Flags positive tabindex values that create unexpected keyboard order.
landmark_main
INFO
Flags pages missing a main landmark.
broken_links
CRITICAL
Probes page links and flags genuinely broken internal and external destinations.
merchant_listing
WARNING
Flags product pages missing merchant-listing structured-data fields.
soft_404
WARNING
Flags 200-OK pages that look like not-found or unavailable content.
duplicate_metadata
WARNING
Flags duplicate titles and descriptions across scanned pages.
aeo_snippet_eligibility
WARNING
Checks whether the page has extractable answer-like copy for AI summaries.
aeo_answer_structure
WARNING
Checks for concise answer blocks near headings and key questions.
aeo_entity_clarity
WARNING
Checks whether brand, organization, and topic entities are named clearly.
aeo_question_coverage
INFO
Checks whether likely user questions are answered directly on the page.
aeo_supporting_evidence
WARNING
Checks for citations, sources, or evidence that support factual claims.
aeo_freshness_signals
INFO
Checks for visible update dates and machine-readable freshness signals.
aeo_author_attribution
INFO
Checks for author, publisher, or organization attribution.
aeo_canonical_consistency
WARNING
Checks whether canonical and citation-target signals agree.
aeo_content_demarcation
INFO
Checks whether main content is clearly separated from navigation and boilerplate.
aeo_speakable_schema
INFO
Checks for optional speakable or answer-friendly structured data.
aeo_llms_txt
INFO
Checks for llms.txt discovery and basic readability.
aeo_crawler_access
WARNING
Checks whether robots.txt blocks common AI answer-engine crawlers.

Health score formula

The score starts at 100 and decays as issues pile up. Weights are tuned so a handful of criticals tank the score faster than a long tail of info noise.

formula.txt
score = max(0, 100 − (critical × 15) − (warning × 5) − (info × 1))
[ EXAMPLE_A ]
2·crit + 5·warn + 12·info
=
33
[ EXAMPLE_B ]
0·crit + 2·warn + 3·info
=
87
[ EXAMPLE_C ]
0·crit + 0·warn + 0·info
=
100

Issue response shape

Individual issues from /monitors/:mid/issues include the rule ID, severity, URL, a short message, a recommendation, and optional technical evidence.

issue.json
{
  "id": "iss_01H...",
  "monitor_id": "mon_01H...",
  "scan_id": "scan_01H...",
  "rule": "broken_status",
  "severity": "critical",
  "url": "https://yoursite.com/products/legacy",
  "message": "Returned 404 Not Found",
  "recommendation": "Either restore the page or add a 301 redirect to the replacement URL.",
  "evidence": {
    "status_code": 404,
    "response_time_ms": 182,
    "final_url": "https://yoursite.com/products/legacy"
  },
  "first_seen_at": "2026-04-12T09:14:22Z",
  "last_seen_at": "2026-04-13T09:14:22Z"
}

One-click fixes (live page is the source of truth)

A recommendation or health issue can be turned into a published fix, one page at a time. For a page VectraSEO publishes, the fixer reads your live page as the source of truth (not a stored copy), applies the minimal patch, re-validates it through the same quality gates as a normal publish, then republishes through your CMS adapter and requests a recrawl.

Because the patch is built on the current live version, edits you made outside VectraSEO are kept, not overwritten. If the live page can't be read (it's down or blocked) or its editable content can't be isolated confidently, the fix is skipped rather than applied from an older copy — and the result tells you why. Deterministic fixes (broken links, JSON-LD schema, image alt text) run on any paid plan; model-written fixes (metadata, answer blocks, internal links) require a paid plan with credits.

[ CH_05.05 / CMS_ADAPTERS ]

Supported CMS platforms

VectraSEO publishes directly to seven CMS options, including Zapier for the long tail. Configure your CMS type and credentials in project settings.

ADAPTER_01
WordPress
<wordpress>

Publishes via the WordPress REST API. Featured images are uploaded to your WordPress media library automatically and attached to the post.

[ CREDENTIALS ]
site_urlusernameapp_password
[ SETUP ]
  1. 01Go to your WordPress admin → Users → Profile → Application Passwords.
  2. 02Enter a name (e.g. "VectraSEO") and click Add New Application Password.
  3. 03Copy the generated password — it will only be shown once.
  4. 04In VectraSEO project settings, enter your site_url, username, and the application password.
Requires WordPress 5.6+ with Application Passwords enabled. Your WordPress site must have HTTPS and the REST API accessible at /wp-json/wp/v2.
ADAPTER_02
Wix
<wix>

Uses the Wix Headless CMS API. VectraSEO creates a draft post and automatically publishes it in a single flow.

[ CREDENTIALS ]
api_keysite_id
[ SETUP ]
  1. 01In your Wix dashboard, go to Settings → API Keys and create a new key with Blog permissions.
  2. 02Copy your API Key and Site ID (found in Settings → General under "Site ID").
  3. 03Paste both values into VectraSEO project settings.
The API key needs Blog → Posts read/write permissions. Wix handles slug generation from the post title.
ADAPTER_03
Shopify
<shopify>

Publishes to your Shopify store's blog via the GraphQL Admin API. Featured images use Shopify's native article image field.

[ CREDENTIALS ]
shop_domainblog_id
[ SETUP ]
  1. 01Install VectraSEO from Shopify Admin and approve the write_content permission.
  2. 02Find your Blog ID: go to Online Store → Blog posts, click your blog, and copy the numeric ID from the URL.
  3. 03Enter shop_domain and blog ID in project settings. The OAuth access token is stored server-side and is never shown in the form.
Shopify publishing uses the OAuth token from the installed app. If publishing fails, reopen VectraSEO from Shopify Admin and confirm the app is connected.
ADAPTER_04
Squarespace
<squarespace>

Publishes directly to a Squarespace blog collection via the Squarespace API. Posts are published immediately (not as drafts).

[ CREDENTIALS ]
api_keysite_idcollection_id
[ SETUP ]
  1. 01In your Squarespace site, go to Settings → Developer API Keys and generate a new API key.
  2. 02Find your Site ID in Settings → General.
  3. 03Find your Collection ID: go to Pages, click your blog page, and note the ID.
  4. 04Enter all three values in VectraSEO project settings.
Requires a Squarespace Business plan or higher for API access. The API key needs content management permissions.
ADAPTER_05
Blogger
<blogger>

Publishes via the Google Blogger API v3. Posts are published immediately with tags applied as labels. Featured images are embedded in the HTML body.

[ CREDENTIALS ]
api_keyblog_id
[ SETUP ]
  1. 01Go to the Google Cloud Console and enable the Blogger API v3.
  2. 02Create an API Key under Credentials (restrict it to the Blogger API for security).
  3. 03Find your Blog ID: open your Blogger dashboard, go to Settings, and copy the numeric blog ID from the URL.
  4. 04Enter the API key and blog ID in VectraSEO project settings.
The API key must have Blogger API v3 enabled in Google Cloud Console. Posts are published as the blog owner.
ADAPTER_06
Zapier
<zapier>

Sends post data to a Zapier webhook, letting you route content to any of Zapier's 6,000+ app integrations. Payloads include an action field (publish, update, or delete) plus full post data.

[ CREDENTIALS ]
webhook_url
[ SETUP ]
  1. 01In Zapier, create a new Zap and choose Webhooks by Zapier as the trigger.
  2. 02Select Catch Hook and copy the generated webhook URL.
  3. 03Paste the webhook URL into VectraSEO project settings.
  4. 04Set up your Zap's action steps (e.g. post to Ghost, Notion, email, Slack).
  5. 05Test by publishing a post from VectraSEO — the payload will appear in Zapier for mapping.
The webhook receives full post data including title, seo_title, body_html, slug, meta_description, excerpt, tags, images (CloudFront URLs), and project context. Use the action field to distinguish between publish, update, and delete events.
ADAPTER_07
Custom API
<custom_api>

Publishes to any REST endpoint. Configurable authentication, path, and retry logic with exponential backoff on server errors.

[ CREDENTIALS ]
endpoint_urlapi_keyauth_headerpublish_pathcapability_contract_urlauthor
[ SETUP ]
  1. 01Ensure your target API accepts POST requests with a JSON body (see payload format below).
  2. 02Enter your endpoint_url (base URL, no trailing slash) and api_key in project settings.
  3. 03Optionally set auth_header (defaults to Authorization: Bearer), publish_path, author, and capability_contract_url.
  4. 04Publish a product capability contract at /.well-known/product-capabilities.json (unsupported capabilities should include blocked_claims, allowed_qualifiers, and preferred_wording). VectraSEO fetches it before generating and writing, so posts avoid your blocked claims from the start. If capability_contract_url is unset, VectraSEO looks for the contract at your project’s site root.
  5. 05When you reject a publish for a claim your product can’t back, return 422 with a structured violations body (see below). VectraSEO remembers each rejected claim on the project and stops re-generating it — an opaque 400 with no body just gets retried on the next post.
  6. 06Your API should return JSON with id and url fields on success.
VectraSEO retries up to 3 times with exponential backoff (2s, 4s, 8s) on 5xx errors. 4xx errors fail immediately and keep the response body so you can see why. The payload includes separate reader and SEO metadata: title is the natural post/H1 title, seo_title is the search-result title, meta_description is the search snippet, and excerpt is the reader-facing summary. Images are sent as base64-encoded data. Requests use HTTP/2 with a 120-second timeout. The capability contract is enforced at two points: generation prompts avoid your blocked claims, and a deterministic pre-publish guard holds any post that still asserts one (remediating to your preferred wording when safe). Claims a destination reports in a 422 rejection are learned per project and fed back into the next generation.
[ CH_05.06 / CUSTOM_API ]

Publishing to any endpoint

The Custom API adapter lets you ship posts to any REST endpoint. VectraSEO POSTs JSON with the following payload shape. For destinations with product capability rules, expose a public /.well-known/product-capabilities.json contract and save its URL in project settings so unsupported claims can be remediated or held before publish.

POST /posts — payload.json
{
  "title": "Post Title",
  "seo_title": "Search Result Title",
  "body_html": "<p>Full HTML content...</p>",
  "slug": "post-title",
  "tags": ["seo", "blogging"],
  "meta_description": "Useful search snippet for this post.",
  "excerpt": "Reader-facing post summary.",
  "author": "Author Name",
  "images": [
    {
      "filename": "image.png",
      "data": "<base64-encoded>",
      "alt": "Alt text"
    }
  ],
  "published_at": "2026-04-05T00:00:00Z",
  "status": "published"
}

Configuration fields

FIELD
REQ
DESCRIPTION
endpoint_url
YES
Base URL of the target API (e.g. https://api.mysite.com)
api_key
YES
Authentication key sent with each request
auth_header
opt
Header name for API key. Default: Authorization (Bearer prefix).
publish_path
opt
POST path appended to base URL. VECTRASEO preset: /blog/publish
capability_contract_url
opt
Public product capability contract, usually /.well-known/product-capabilities.json
author
opt
Author value sent in the publish payload. Defaults to project business name.

Product capability contract

Use this optional public JSON contract to tell VectraSEO which destination-product capabilities are supported, unsupported, planned, beta, deprecated, or unknown.

GET /.well-known/product-capabilities.json
{
  "schema_version": "1.0",
  "product": { "name": "Emcognito", "aliases": ["Emcognito aliases"] },
  "updated_at": "2026-06-12T00:00:00Z",
  "cache_ttl_seconds": 3600,
  "capabilities": [
    {
      "id": "custom_domains",
      "name": "Custom domains",
      "status": "unsupported",
      "blocked_claims": ["custom domain", "custom domains"],
      "allowed_qualifiers": ["does not support", "planned", "roadmap", "subdomain"],
      "preferred_wording": "Emcognito aliases currently use the shared emcognito.com domain; custom subdomain support is planned, but custom domains are not available today."
    }
  ]
}

Rejection response (tell VectraSEO what to avoid)

When you reject a publish because the post claims something your product can’t back, return 422 with a structured violations array. VectraSEO records each rejected claim on the project and excludes it from future generations. A bare 400 with no body is treated as a transient failure and the same claim may be generated again.

POST /blog/publish → 422
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
  "error": "capability_claim_violation",
  "message": "Post asserts claims this product cannot back.",
  "violations": [
    {
      "capability_id": "open_source",
      "matched_claim": "open source",
      "status": "unsupported",
      "field": "body_html",
      "preferred_wording": "Sendant's source code is not public; do not describe it as open source."
    }
  ]
}
[ END_OF_MANUAL ]
[ NEED_HELP? ]

Can't find what
you're looking for?

Reach out to the team. We answer every email and we read every bug report.