Paste-a-URL site audit tools are a popular product: type in a web address, get a score and a list of fixes written for a non-technical owner. The hard part is the audit engine. If you rent that, the rest is a small web app. This series builds one in three parts:

  1. A report page from a URL (this post)
  2. PDF export with your own branding
  3. A free preview with a paid unlock

What you need

  • Python 3.10+ with flask and requests
  • An API key from seoscoreapi.com. The free tier's two audits a day is enough to build with.

The whole app

import os
from html import escape

import requests
from flask import Flask, request

API = "https://seoscoreapi.com/audit"
KEY = os.environ["SEOSCORE_API_KEY"]
app = Flask(__name__)

FORM = """<form><input name="url" placeholder="https://example.com" size="40">
<button>Audit</button></form>"""
OWNERS = {"developer": "Your developer", "content": "Whoever writes the site", "marketing": "Marketing"}


def run_audit(url: str) -> dict:
    r = requests.get(API, params={"url": url}, headers={"X-API-Key": KEY}, timeout=60)
    r.raise_for_status()
    return r.json()


@app.get("/")
def home():
    url = request.args.get("url", "").strip()
    if not url:
        return FORM
    data = run_audit(url)
    rows = "".join(
        f"<li><strong>{escape(p['plain_english'])}</strong><br>"
        f"<small>{escape(OWNERS.get(p['who_fixes_it'], ''))} &middot; {escape(p['severity'])}"
        f" &middot; {escape(p['fix'])}</small></li>"
        for p in data.get("priorities", [])
    )
    return (f"{FORM}<h1>{escape(data['url'])}: {round(data['score'])}/100 ({escape(data['grade'])})</h1>"
            f"<h2>What to fix</h2><ol>{rows or '<li>Nothing urgent.</li>'}</ol>")


if __name__ == "__main__":
    app.run(debug=True)

Run it with SEOSCORE_API_KEY=... python app.py and open http://127.0.0.1:5000.

What is doing the work

The response from /audit has an overall score and grade, and a priorities list sorted by importance. Each priority carries four fields this app uses:

Field Example
plain_english "The page has no summary for search results, so Google picks a random snippet that may not make people click."
who_fixes_it content
severity high
fix "Add a meta description of 120-160 characters"

The app leads with the plain sentence and puts the technical fix in small type underneath. That ordering is the whole product idea: the owner reads the first line, and whoever does the work reads the second.

Two things to add before real users see it

Validate the input. The API refuses private and malformed addresses for you, but your own app should still reject empty input and show a friendly message when the API returns a 422 because the site couldn't be loaded:

    r = requests.get(API, params={"url": url}, headers={"X-API-Key": KEY}, timeout=60)
    if r.status_code == 422:
        return {"error": r.json().get("detail", "That site could not be loaded.")}

Cache results. An audit takes a couple of seconds and counts against your quota. Store each result by URL for a day so a reload doesn't spend another one.

Next

The report is on screen. In part 2 it becomes a PDF with your logo on it.