Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mavera.io/llms.txt

Use this file to discover all available pages before exploring further.

Scenario

Report on ranking changes — which keywords improved, which dropped, and what to do. Pull current positions from domain_organic, compare against a stored baseline, then send change data to Mave for an executive-ready narrative report.

Architecture

Code

import os, requests, csv, io, json
from pathlib import Path

SR, MV = os.environ["SEMRUSH_API_KEY"], os.environ["MAVERA_API_KEY"]
MB = "https://app.mavera.io/api/v1"
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
DOMAIN, BF = "yourdomain.com", "semrush_baseline.json"

resp = requests.get("https://api.semrush.com/", params={
    "type": "domain_organic", "key": SR, "domain": DOMAIN,
    "database": "us", "display_limit": 100, "display_sort": "tr_desc",
    "export_columns": "Ph,Po,Pp,Nq,Cp,Ur,Tr,Tc,Co,Kd",
})
reader = csv.reader(io.StringIO(resp.text), delimiter=";")
next(reader)
cur = {}
for r in reader:
    if len(r) < 10: continue
    cur[r[0]] = {"kw": r[0], "pos": int(r[1] or 100), "prev": int(r[2] or 100),
                 "vol": int(r[3] or 0), "url": r[5], "tr": float(r[6] or 0)}

bl = json.loads(Path(BF).read_text()) if Path(BF).exists() else {}
up, down, new, lost = [], [], [], []
for kw, d in cur.items():
    if kw in bl:
        d["chg"] = bl[kw]["pos"] - d["pos"]
        (up if d["chg"] > 0 else down if d["chg"] < 0 else []).append(d)
    else: d["chg"] = 0; new.append(d)
for kw in bl:
    if kw not in cur: lost.append({"kw": kw, "pos": bl[kw]["pos"], "vol": bl[kw].get("vol", 0)})

up.sort(key=lambda x: x["chg"], reverse=True)
down.sort(key=lambda x: x["chg"])
Path(BF).write_text(json.dumps({k: {"pos": v["pos"], "vol": v["vol"]} for k, v in cur.items()}, indent=2))

def fmt(m):
    return "\n".join(f"- \"{x['kw']}\" (vol: {x['vol']}): {x.get('prev','?')}{x['pos']} "
                     f"({'+' if x.get('chg',0)>0 else ''}{x.get('chg',0)})" for x in m[:10])

total_tr = sum(d["tr"] for d in cur.values())
t10 = sum(1 for d in cur.values() if d["pos"] <= 10)
report = (f"RANKING: {DOMAIN} | KWs: {len(cur)} | Traffic: {total_tr:.0f} | Top10: {t10}\n"
          f"Up: {len(up)} | Down: {len(down)} | New: {len(new)} | Lost: {len(lost)}\n\n"
          f"IMPROVED:\n{fmt(up)}\n\nDECLINED:\n{fmt(down)}")

mave = requests.post(f"{MB}/mave/chat", headers=MH, json={
    "message": f"Executive SEO report.\n\n{report}\n\n"
               f"Include: 1) Summary (3 sentences) 2) Wins 3) Risks "
               f"4) Priority actions for next 2 weeks. Plain language for VP Marketing.",
}).json()
print(mave.get("content", ""))

Example Output

Summary: 23 up, 14 down, 8 new. Traffic: 12,400/mo. 31 in top 10.

Wins: "marketing persona tool" 18→6 (+12, 2400 vol). "content testing
  platform" 24→11 (+13). Close to page 1 — backlinks needed.
Risks: "focus group software" 8→15 (-7). Lost ~800/mo. Check competitors.

Actions: 1) Audit /product/focus-groups 2) Build backlinks for "content
  testing platform" 3) Internal-link to new ranking pages 4) Weekly alerts

Error Handling

On first run, all keywords appear as “new.” Run twice with a gap (daily/weekly) for meaningful change data.
Match the database parameter to your target market. US rankings won’t match UK Search Console data.
Delete the baseline file and rebuild after domain migrations, URL restructures, or redirect changes.