
Developer API
REST API
Authenticate with your API key, submit a URL, get results via webhook or polling. Available on the 250-scan plan and up.
Base URL
https://www.seolint.dev/api/v1
Authentication
Pass your API key as a Bearer token in every request. Generate your key from the API dashboard.
curl -X POST https://www.seolint.dev/api/v1/scan \
-H "Authorization: Bearer sl_your_api_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'Rate limits
Limits mirror your plan's monthly scan quota. There's no per-second rate limit, since each scan takes 15 to 45 seconds and concurrency caps naturally. The REST API and MCP server share the same quota per API key.
| Plan | Scans / month | Concurrent |
|---|---|---|
| Free preview | 1 | 1 |
| SEOLint / $79 | 100 | 5 |
| Custom | Negotiated | Negotiated |
HTTP 429 with a Retry-After header set to the seconds remaining until quota resets. Back off and retry after that delay instead of hammering the endpoint./scan
Submit a URL for scanning. Returns immediately. Results arrive via webhook or polling.
curl -X POST https://www.seolint.dev/api/v1/scan \
-H "Authorization: Bearer sl_your_api_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'Response
{
"scanId": "3f2a1b4c-...",
"pollUrl": "https://www.seolint.dev/api/v1/scan/3f2a1b4c-...",
"reportUrl": "https://www.seolint.dev/scan/3f2a1b4c-..."
}/scan/:id
Poll until status is complete. Recommended interval: 3 seconds.
curl https://www.seolint.dev/api/v1/scan/3f2a1b4c-...
Response (complete)
{
"status": "complete",
"issues": [
{
"id": "missing-title",
"category": "seo",
"severity": "critical",
"title": "Missing page title",
"description": "The page has no <title> tag...",
"fix": "Add a unique <title> tag under 60 characters."
}
],
"markdown": "# Website Audit: https://example.com\n\n..."
}/scan/:id/markdown
Returns the report as a raw .md file. Ideal for piping into AI agents, Claude, or Cursor.
curl https://www.seolint.dev/api/v1/scan/3f2a1b4c-.../markdown
Memory & site intelligence
Every scan builds a persistent picture of the site: goal, audience, sitemap structure, and content gaps. These endpoints expose that memory so Claude (or your own agent) can use it without re-scanning.
Full intelligence picture for a domain. Start every SEO session with this. Returns site profile (goal, ICP, niche), sitemap breakdown, cross-page patterns, scan coverage, and a summary of missing pages.
curl "https://www.seolint.dev/api/v1/site-intelligence?domain=example.com" \ -H "Authorization: Bearer sl_your_api_key"
Response
{
"domain": "example.com",
"profile": {
"goal": "Help developers ship faster with better tooling",
"icp": "Solo developers and small teams using Claude daily",
"primary_keyword": "developer SEO tools",
"niche": "developer tooling",
"business_model": "B2B",
"business_type": "SaaS"
},
"sitemap": {
"total_urls": 84,
"page_type_counts": { "blog_post": 20, "homepage": 1, "pricing": 1 },
"haiku_insights": "Strong blog presence but no comparison pages...",
"unscanned_urls": ["/blog/seo-checklist", "/blog/fix-lcp"]
},
"page_suggestions": [
{ "slug": "/vs/ahrefs", "title": "SEOLint vs Ahrefs", "target_keyword": "ahrefs alternative" }
],
"markdown": "# Site Intelligence: example.com\n\n..."
}markdown field is formatted for Claude. Paste it directly into your conversation for instant context.Missing page suggestions generated from sitemap analysis and site profile. Each suggestion includes a copy-paste brief for creating the page in Claude or Cursor. Generated on first scan, updated automatically when new scan data is available.
curl "https://www.seolint.dev/api/v1/suggest-pages?domain=example.com" \ -H "Authorization: Bearer sl_your_api_key"
Response
{
"domain": "example.com",
"suggestions": [
{
"slug": "/blog/fix-missing-h1",
"title": "How to Fix Missing H1 Tags",
"cluster": "technical SEO",
"intent": "informational",
"target_keyword": "fix missing h1 tag",
"reason": "You cover H1 theory but have no how-to content for fixing it",
"brief": "Write a developer-focused post titled 'How to Fix Missing H1 Tags'..."
}
],
"total_sitemap_urls": 84,
"analyzed_at": "2026-04-05T10:00:00.000Z",
"markdown": "# Page Suggestions: example.com\n\n..."
}brief field. Paste it directly into Claude or Cursor to create the page immediately.Additional endpoints
/open-issues?url=https://example.comAll unresolved issues for a URL — no re-scan needed/history?url=https://example.comScan history with NEW / FIXED / PERSISTING diffs/site-status?url=https://example.comTrend + rescan recommendation for a URL/sitesAll domains you've scanned with health summary/scan/:id/resolveMark issue IDs as fixed: { issueIds: ["missing-title"] }Webhooks
Set a webhook URL in your API dashboard and we'll POST results to your endpoint when each scan finishes. No polling needed.
Events
scan.completedScan finished successfullyscan.failedScan encountered an errorPayload
{
"event": "scan.completed",
"scan_id": "3f2a1b4c-...",
"url": "https://example.com",
"status": "complete",
"issue_count": 8,
"critical_count": 2,
"report_url": "https://www.seolint.dev/scan/3f2a1b4c-...",
"timestamp": "2026-03-30T12:00:00.000Z"
}Verifying signatures
Every request includes an X-SEOLint-Signature header. Verify it with your signing secret (shown in the API dashboard) to confirm the request came from us.
const crypto = require('crypto')
function isValidWebhook(rawBody, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody) // rawBody must be the raw string, not parsed JSON
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
)
}
// Express
app.post('/webhooks/scanner', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-seolint-signature']
if (!isValidWebhook(req.body, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature')
}
const event = JSON.parse(req.body)
console.log(event.event, event.url, event.critical_count)
res.sendStatus(200)
})Read the raw body first