Scopes
API key scopes control which endpoints a key can access. A key without the required scope for an endpoint receives a 403 Forbidden response.
Scope reference
Section titled “Scope reference”| Scope | Description | Grants access to |
|---|---|---|
audit:read | Read audit findings, crawl results, and issue lists | GET /v1/projects/:id/audits, GET /v1/projects/:id/issues |
projects:read | List and read project metadata | GET /v1/projects, GET /v1/projects/:id |
backlinks:read | Access backlink profile and referring domain data | GET /v1/backlinks/:target, POST /v1/backlinks/batch |
rankings:read | Access keyword position data and rank history | GET /v1/projects/:id/rankings, GET /v1/projects/:id/keywords |
connectors:read | Read connector sync status and synced data snapshots | GET /v1/projects/:id/connectors, GET /v1/projects/:id/gsc |
How to create a key with the right scopes
Section titled “How to create a key with the right scopes”Keys are created in the dashboard (there is no create-key API endpoint — see API Keys). Work backwards from the endpoints your integration actually calls, then grant exactly those scopes and nothing more.
Worked example — a monitoring dashboard that reads audit findings and current rankings:
- List the endpoints the integration calls. Say it hits
GET /v1/projects(to enumerate projects),GET /v1/projects/:id/issues(to read findings), andGET /v1/projects/:id/rankings(to read positions). - Map each endpoint to its scope using the table above:
GET /v1/projects→projects:read;GET /v1/projects/:id/issues→audit:read;GET /v1/projects/:id/rankings→rankings:read. - In the dashboard, go to Account → API Keys → Create API key. Name it for the integration (e.g.,
grafana-monitoring) so a future audit of active keys is self-explanatory. - Check exactly these three scopes —
projects:read,audit:read,rankings:read— and leavebacklinks:readandconnectors:readunchecked. The dashboard does not pre-select a “full access” set; you choose each one deliberately. - Set an expiry if the key lives in a temporary or lower-trust environment. Click create, copy the
vis_live_*value once, and store it in your secrets manager.
Verify the new key works and is correctly scoped before you wire it into anything:
# Should succeed (projects:read granted)curl -s -o /dev/null -w "%{http_code}\n" \ "https://visibilityiq365.com/api/v1/projects" \ -H "Authorization: Bearer vis_live_YOUR_KEY_HERE"# → 200
# Should fail with 403 (backlinks:read NOT granted — this is correct)curl -s -o /dev/null -w "%{http_code}\n" \ "https://visibilityiq365.com/api/v1/backlinks/example.com" \ -H "Authorization: Bearer vis_live_YOUR_KEY_HERE"# → 403The second call returning 403 is the confirmation that least privilege is actually in effect — the key can reach what it needs and is denied what it doesn’t.
Debugging a 403
Section titled “Debugging a 403”A 403 Forbidden means the key authenticated successfully but lacks the scope for the endpoint. (Contrast with 401 Unauthorized, which means the key itself is missing, malformed, revoked, or expired — that is an auth problem, not a scope problem.) The response body names the scope that was required:
{ "ok": false, "error": { "code": "insufficient_scope", "message": "This endpoint requires the 'backlinks:read' scope.", "requiredScope": "backlinks:read" }}Walk through it in order:
- Read
requiredScopefrom the error body. That field tells you exactly which scope to add — you don’t have to guess from the endpoint path. - Confirm what the key actually has. Go to Account → API Keys and check the scopes listed for the key, or look at the key you minted for this integration. Scopes are fixed at creation and shown next to each key.
- Decide: widen the key, or use a different key. If the integration legitimately needs the data, the safe move is usually to mint a new key with the added scope and rotate to it (see API Keys → Rotating a key) rather than broadening a widely-deployed key. If the call was a mistake (the integration shouldn’t be hitting that endpoint at all), fix the caller — don’t add the scope.
- Re-test with the curl one-liner above before redeploying. A
200confirms the fix; a repeated403means you’re still presenting the old key (check your environment variable wasn’t cached) or you added the wrong scope.
Common causes, in rough order of frequency:
| Symptom | Likely cause | Fix |
|---|---|---|
403 on an endpoint the key used to reach | A new key was deployed with narrower scopes than the old one | Compare scopes; re-mint with the missing scope |
403 only on backlinks:read/rankings:read endpoints | Key was created scoped to audits/projects only | Add the data scope on a new key |
403 immediately after a new endpoint launched | New endpoints require explicitly-added scopes; existing keys are not auto-upgraded | Add the new scope to a key (see Future scopes) |
401, not 403 | Not a scope issue — key missing/expired/revoked | See API Keys and Auth overview |
Principle of least privilege
Section titled “Principle of least privilege”Grant each key only the scopes it needs. A key used only to fetch backlink data doesn’t need audit:read or rankings:read. Narrower scopes reduce the blast radius if a key is compromised — a leaked read-only backlinks key cannot be used to read your audit findings or ranking data.
Concrete scenarios:
- CI/CD gate. A pipeline that triggers an audit and blocks the build on new Critical findings needs only
audit:readandprojects:read(and, once write scopes ship,audit:writeto start the crawl). It does not needbacklinks:read,rankings:read, orconnectors:read. If the CI runner is compromised, the attacker gets read access to findings and nothing else. - Backlink reporting job. A nightly job that pulls referring-domain profiles needs
backlinks:readandprojects:readonly. Withholdaudit:readandrankings:readso a leak of this key (which often lives on a shared reporting box) can’t expose your full SEO posture. - Embedded/partner dashboard. A read-only widget you embed for a client should get the single narrowest scope that powers it — often just
rankings:read+projects:read. Never reuse your internal full-coverage key here. - One key per integration. Don’t share one broadly-scoped key across CI, monitoring, and reporting. Separate keys mean you can revoke one integration’s access without breaking the others, and last-used timestamps tell you which key to rotate after an incident.
Scope combinations
Section titled “Scope combinations”You can combine any number of scopes on a single key. There is no “full access” wildcard scope — you must explicitly list each scope needed.
Scopes: audit:read, projects:readThis key can list projects and read audit findings but cannot access backlinks or rankings.
Future scopes
Section titled “Future scopes”Additional scopes will be added as new API endpoints are released. Existing keys are not automatically granted new scopes — you must explicitly add new scopes to existing keys or create new keys.
Planned scopes (not yet available):
audit:write— trigger crawls and mark findingsconnectors:write— trigger connector syncsalerts:read— access alert configurations and triggered alerts