SEOking

For agencies and platforms

Audit API

Run the full SEOking audit programmatically and get every finding back as JSON: scores, severities, evidence and the affected URLs. Built for agencies auditing a client book, and for platforms that want to show their users what's wrong with their site.

Try it in the browser first

Authentication

Every request carries your key in an X-API-Key header. Keys are issued with a daily quota. Ask us for one.

Start an audit

curl -X POST http://18.202.16.187/api/v1/scan \
  -H "X-API-Key: bza_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'
{
  "job_id": "wU3f...",
  "domain": "example.com",
  "state": "queued",
  "poll":  "http://18.202.16.187/api/v1/job/wU3f..."
}

Poll until it's done

Audits crawl multiple pages and wait on Google's PageSpeed API, so they run asynchronously. Poll every second or two; a typical audit finishes in 30–90 seconds.

curl http://18.202.16.187/api/v1/job/wU3f... \
  -H "X-API-Key: bza_your_key_here"
{ "state": "running", "percent": 62, "message": "Crawled 7/11 pages" }

Fetch the report

curl http://18.202.16.187/api/v1/report/example.com \
  -H "X-API-Key: bza_your_key_here"
{
  "domain": "example.com",
  "score": 71, "grade": "C", "verdict": "Needs work",
  "counts": { "checks": 58, "passed": 41, "failed": 15, "critical": 2 },
  "categories": {
    "security":    { "label": "Security & trust",     "score": 88 },
    "email":       { "label": "Email deliverability", "score": 42 }
  },
  "findings": [
    {
      "key": "spf_single",
      "category": "email",
      "label": "Exactly one SPF record",
      "state": "fail",
      "severity": "critical",
      "detail": "2 SPF records are published. RFC 7208 allows exactly one...",
      "fix": "Delete both records and publish one merged record...",
      "evidence": "v=spf1 include:_spf.google.com ~all\nv=spf1 include:...",
      "affected": []
    }
  ]
}

Batch auditing

For an agency auditing a client book. Returns a portfolio URL and a CSV. Batches run in their own lane, one domain at a time, so the interactive audit stays responsive, so expect roughly a minute per domain.

curl -X POST http://18.202.16.187/api/v1/batch \
  -H "X-API-Key: bza_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"label": "Client book", "domains": "one.com\ntwo.com\nthree.com"}'
{
  "batch_id": 12, "accepted": 3, "rejected": [],
  "portfolio": "http://18.202.16.187/batch/<token>",
  "csv":       "http://18.202.16.187/batch/<token>.csv"
}

Webhooks

Register an endpoint and stop polling. Events: scan.completed, batch.completed, monitor.regression.

curl -X POST http://18.202.16.187/api/v1/webhooks \
  -H "X-API-Key: bza_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://yourapp.example/hooks/bz",
       "events": ["monitor.regression"]}'

The response carries a secret, shown once. Every delivery is signed:

X-BZ-Event:     monitor.regression
X-BZ-Delivery:  d7Kx9…            unique per delivery; deduplicate on this
X-BZ-Timestamp: 1786930000
X-BZ-Signature: sha256=…          HMAC-SHA256 over "{timestamp}.{raw body}"

Verify in your own code before trusting a payload:

expected = hmac.new(secret, f"{timestamp}.".encode() + raw_body,
                    hashlib.sha256).hexdigest()
assert hmac.compare_digest(f"sha256={expected}", signature)
assert abs(time.time() - int(timestamp)) < 300     reject replays

Delivery semantics

Response codes

CodeMeaning
200Success.
400The domain could not be parsed, or resolves to a private network.
401Missing or revoked API key.
404No audit exists for that domain yet. POST /api/v1/scan first.
429Daily quota reached.

Notes