Developer API

Automate website audits

Authenticate with your API key, submit a URL, get results via webhook or polling. Available on the Agency plan.

Base URL

https://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://seolint.dev/api/v1/scan \
  -H "Authorization: Bearer sl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}'
401Missing or invalid API key
403API key valid but no active subscription
POST

/scan

Submit a URL for scanning. Returns immediately. Results arrive via webhook or polling.

curl -X POST https://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://seolint.dev/api/v1/scan/3f2a1b4c-...",
  "reportUrl": "https://seolint.dev/report/3f2a1b4c-..."
}
GET

/scan/:id

Poll until status is complete. Recommended interval: 3 seconds.

curl https://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..."
}
GET

/scan/:id/markdown

Returns the report as a raw .md file. Ideal for piping into AI agents, Claude, or Cursor.

curl https://seolint.dev/api/v1/scan/3f2a1b4c-.../markdown

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 successfully
scan.failedScan encountered an error

Payload

{
  "event": "scan.completed",
  "scan_id": "3f2a1b4c-...",
  "url": "https://example.com",
  "status": "complete",
  "issue_count": 8,
  "critical_count": 2,
  "report_url": "https://seolint.dev/report/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.

// Node.js verification example
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)
})

Important: Always read the raw request body before parsing JSON. Parsing then re-stringifying changes the byte sequence and breaks the HMAC check.

Status codes

200OK: request succeeded
202Accepted: scan not yet complete (markdown endpoint)
400Bad request: missing or invalid url
401Unauthorized: missing or invalid API key
403Forbidden: no active subscription
404Not found: scan ID does not exist
500Server error: scan failed to start