A SaaS marketing site is the highest-edit-velocity surface most companies own. Pricing changes monthly. Landing pages spin up for every campaign. Docs get restructured every quarter. The blog gets a new template twice a year. Every one of those edits is a chance to break SEO in a way nobody on the team would catch — because nobody on the team is responsible for the SEO of all of these pages at once.

This post is the continuous monitoring setup we recommend for SaaS marketing teams: weekly audits across every important page, Slack alerts on regression, monthly summary for the marketing lead. End-to-end, ~60 lines of Python and one cron job.

The ICPs this is for

You should read this if you're at a SaaS company with:

  • A marketing site with 50+ "live" landing pages (homepage, product pages, /pricing, /docs, /use-cases, /customers, /integrations).
  • Multiple teams pushing changes — PMM, engineering, content, design — without a single SEO owner gating all of them.
  • An organic-search-driven acquisition channel where a ranked page going stale is worth real ARR.

If that's you, you've probably already been bitten at least once by a page going from "ranks in the top 3 for a high-intent query" to "ranks on page 2 and we don't know why." This setup is designed to make that question answerable within hours instead of months.

What to monitor

The wrong answer is "every page on the site." The right answer is a curated list of ~30-50 pages that actually matter, grouped:

Tier 1 — Revenue-critical (5-10 pages): Homepage, /pricing, /signup, top product pages. Audit daily.

Tier 2 — Acquisition surfaces (15-25 pages): Top landing pages by organic traffic, comparison pages, integration pages. Audit weekly.

Tier 3 — Long tail (everything else): Blog, docs, changelog. Audit monthly via sitemap.

Auditing everything weekly is wasteful. Tier 1 daily / Tier 2 weekly / Tier 3 monthly is what most SaaS teams actually want.

The monitoring script

# monitor.py
import os, requests, json, time
from datetime import datetime

API_KEY = os.environ["SEOSCORE_API_KEY"]
BASE = "https://api.seoscoreapi.com"
SITE = "https://yoursite.com"

TIER_1 = [
    f"{SITE}/",
    f"{SITE}/pricing",
    f"{SITE}/signup",
    f"{SITE}/product",
    f"{SITE}/enterprise",
]

TIER_2 = [
    f"{SITE}/integrations/slack",
    f"{SITE}/integrations/notion",
    f"{SITE}/vs/competitor-a",
    f"{SITE}/vs/competitor-b",
    f"{SITE}/use-cases/marketing-teams",
    # ... 20 more
]

def audit_batch(urls):
    r = requests.post(
        f"{BASE}/audit/batch",
        headers={"X-API-Key": API_KEY},
        json={"urls": urls},
        timeout=240,
    )
    r.raise_for_status()
    return r.json()["results"]

def check_regressions(results, threshold=-3.0):
    """Use history deltas to find pages that regressed."""
    alerts = []
    for r in results:
        delta = r.get("history", {}).get("delta", {})
        score_change = delta.get("score", 0)
        if score_change <= threshold:
            alerts.append({
                "url": r["url"],
                "current": r["score"],
                "delta": score_change,
                "categories": delta.get("categories", {}),
            })
    return alerts

def post_slack(alerts):
    if not alerts:
        return
    lines = [f":chart_with_downwards_trend: *SEO regressions today*"]
    for a in alerts:
        worst_cat = min(a["categories"].items(), key=lambda x: x[1])
        lines.append(
            f"• `{a['url']}` → {a['current']} ({a['delta']:+.1f}, "
            f"{worst_cat[0]} {worst_cat[1]:+.1f})"
        )
    requests.post(
        os.environ["SLACK_WEBHOOK"],
        json={"text": "\n".join(lines)},
    )

# Decide which tier to run based on day
now = datetime.utcnow()
urls_to_audit = list(TIER_1)
if now.weekday() == 0:        # Monday: include Tier 2
    urls_to_audit += TIER_2
if now.day == 1:              # First of month: full sweep handled separately
    pass

results = audit_batch(urls_to_audit)
alerts = check_regressions(results)
post_slack(alerts)

Run as a daily cron at 7am UTC. Tier 1 audits run every day, Tier 2 piggybacks on Monday, the monthly Tier 3 sweep is a separate larger job (see sitemap-driven post for that pattern).

What the Slack alert looks like

📉 SEO regressions today
• https://yoursite.com/pricing → 84 (-5.0, performance -8.5)
• https://yoursite.com/vs/competitor-a → 79 (-3.5, seo -4.0)

That's it. Per page: current score, total delta, and the worst-regressed category with its specific delta. The marketing lead opens Slack, sees two regressions, knows which pages to look at, knows which category is at fault.

That third column is what makes the alert actionable. "Performance regressed 8.5" tells you to go look at recent changes to the pricing page that affected LCP or CLS. "SEO regressed 4.0" tells you to look at meta tags, headings, or structured data. Without the per-category breakdown, the alert is just "something is wrong, go investigate."

Monthly executive summary

The Slack alerts handle the day-to-day. The marketing lead also wants a monthly view — "are we trending up or down overall?" The /history/domains endpoint gives you that in one call:

r = requests.get(
    f"{BASE}/history/domains",
    headers={"X-API-Key": API_KEY},
).json()

avg = sum(d["latest_score"] for d in r["domains"]) / len(r["domains"])
improved = sum(1 for d in r["domains"] if d["trend_30d"] > 0)
declined = sum(1 for d in r["domains"] if d["trend_30d"] < 0)
flat     = len(r["domains"]) - improved - declined

print(f"Avg score across {len(r['domains'])} URLs: {avg:.1f}")
print(f"Improved last 30d: {improved}")
print(f"Declined last 30d: {declined}")
print(f"Worst 5 trends: {sorted(r['domains'], key=lambda d: d['trend_30d'])[:5]}")

Generate that as a markdown report on the 1st of each month, paste it into the team's monthly review doc, you've replaced an entire half-day of manual SEO reporting with a 30-second cron job.

When to fire an alert vs when to stay quiet

The biggest mistake we see teams make: alerting on every audit completion. The marketing lead's Slack gets 30 audit-summary messages a day, mutes the channel, and the one message that mattered gets missed.

Rules of thumb:

  • Alert only on regression, not on every audit. No regression = no message.
  • Alert only when delta is outside noise. -3.0 is our recommended floor (see the pre-deploy gate post for why).
  • Aggregate before alerting. One alert per day with all the regressions in it, not one alert per regressed page.
  • Reserve at-mentions for Tier 1. A /pricing regression should @channel. A blog post regression should not.

Tune those thresholds for your team. The goal is "every alert in this channel is worth responding to." If that's not true, the channel gets muted and the system stops working.

Audit volume math

What this actually costs in API calls:

  • Tier 1 daily: 5-10 pages × 30 days = 150-300 audits/month
  • Tier 2 weekly: 20 pages × 4 = 80 audits/month
  • Tier 3 monthly: ~200-500 pages × 1 = 200-500 audits/month

Total: ~430-880 audits/month for the typical mid-size SaaS marketing site. That's Basic ($15/mo) territory with room to spare.

If you're a larger SaaS with a 1,000+ page docs site or aggressive landing-page programmatic SEO (10,000+ generated pages), you'll need Pro ($39/mo) for the audit volume plus the 1-year history retention that lets you do quarterly "what happened to organic this quarter" forensics.

What gets caught

The kinds of issues this setup actually surfaces, from real teams running it:

  • A pricing page A/B test that shipped a variant with degraded LCP. Caught next morning.
  • A docs site search rebuild that injected three render-blocking scripts. Caught on the next deploy.
  • A blog migration that broke canonical tags on 70% of posts. Caught in the weekly sweep.
  • A new "trust banner" that pushed every landing page's hero below the fold and tanked LCP. Caught within 24 hours.
  • A docs reorganization that re-routed 30 high-traffic URLs without 301s. Caught in the monthly sweep (with a separate redirect-check job, but the symptom was visible in the SEO score deltas first).

None of these would have been caught by Search Console for weeks. By the time they were, traffic was already gone and the cause was buried.

Getting started

  1. Pick your Tier 1 list (5-10 pages, revenue-critical).
  2. Pick your Tier 2 list (20-30 pages, acquisition-driving).
  3. Set up the cron + Slack webhook + the script above.
  4. Run for a week. Tune the threshold (-3.0 is the default; some teams want -2.0).
  5. Add the monthly summary.

This whole setup takes an afternoon to build and a quarter to pay for itself. The cost is $15/mo on Basic for the typical SaaS team. The benefit is your marketing site stops silently breaking between deploys.

For larger SaaS teams with bigger surface areas, Pro ($39/mo) gives you the headroom and the history retention that turns this from monitoring into a system of record.