Provisioning API
Create and manage projects programmatically
An account API key reaches every project you own and can create new ones. It can configure a project, run its content pipeline and site scans, apply fixes and publish — over REST, and over MCP for AI assistants. Some dashboard actions stay signed-in only; they are listed under What no key can do below.
Create a key under Account → 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_.
curl -H "x-api-key: vsak_..." https://api.vectraseo.com/api/v1/account/meOnboard 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 and content schedule — together.
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.
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 site-health monitor. The non-destructive equivalent is programmatic instead — disable it. The deletes a key can make remove configuration, never content:
projects:writeremoves a competitor, an AI-visibility monitor or a tracked rank keyword (each can be added back) and can switch the content schedule off, andgaps:deleteremoves a suggested topic, whichPOST /gaps/identifyrebuilds. - Delete or archive a post. Archiving is deletion on a delay — the hourly sweeper purges archived rows after five days — so both stay signed-in actions.
- 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. - Connect or disconnect Google. Search Console and GA4 connection (OAuth consent, property choice, disconnect), and Indexing API credentials need a signed-in session. So do a post's repair actions — export, regenerate images, repair metadata, retry the truth check, remove unconfirmed claims, advance phase, replay, unarchive — and title recommendations.
Push first-party traffic
No Google Analytics or Search Console? Push your own aggregated traffic instead, typically a nightly rollup of your CDN access logs. Use a key with only traffic:write and send one UTC day per request to POST /api/v1/projects/{project_id}/traffic/daily. The data appears on the project card as "Visits (site logs)" when GA4 has no data, in GET /traffic/overview and GET /traffic/pages, and as context on each recommendation.
curl -X POST https://api.vectraseo.com/api/v1/projects/$PROJECT_ID/traffic/daily \
-H "x-api-key: vsak_..." -H "content-type: application/json" \
-d '{
"date": "2026-10-02",
"source": "cloudfront",
"rows": [
{ "path": "/", "referrer_host": "chatgpt.com", "country": "US",
"status_class": "2xx", "agent_class": "human", "agent_name": "Chrome",
"requests": 41, "edge_hits": 39 },
{ "path": "/llms.txt", "status_class": "2xx", "agent_class": "ai_crawler",
"agent_name": "GPTBot", "requests": 6, "edge_hits": 6 }
]
}'- A replay replaces the day. Ingest is idempotent per project, source and date, so retries never inflate counts.
- Each day keeps its 1,000 busiest pages (the rest are reported as
pages_truncated); referrer, UTM, country, crawler and status breakdowns keep their top 50 and fold the rest intoother. - A visit counts as an AI referral when its referrer host or
utm_sourceis an assistant such as chatgpt.com, perplexity.ai, claude.ai or gemini.google.com. - Send aggregates only. No IP addresses, raw user agents, cookies or full query strings; fold referrer or country buckets under 3 requests into
other.
Responses: 200 {accepted, date, source, pages, pages_truncated, unmatched_rows}. 403 when the key lacks traffic:write or the plan is not paid. 413 when the body is over 4 MiB: aggregate further. 422 for a schema violation or a project with no website URL; do not retry. 429: retry after Retry-After. 5xx: retry with backoff.
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-*.
Can't find what
you're looking for?
Reach out to the team. We answer every email and we read every bug report.