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

Pull keyword suggestions from Ahrefs’ Keywords Explorer, cluster them by parent topic, then generate SEO-optimized content at scale — titles, meta descriptions, and outlines for each cluster — using Mavera with a consistent brand voice. This job uses the OpenAI SDK pattern for some Mavera calls to demonstrate compatibility.

Architecture

Code

import os, requests, time
from collections import defaultdict
from openai import OpenAI

AH = os.environ["AHREFS_API_TOKEN"]
MV = os.environ["MAVERA_API_KEY"]
MB = "https://app.mavera.io/api/v1"
AH_H = {"Authorization": f"Bearer {AH}", "Accept": "application/json"}
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
SEEDS, COUNTRY = ["content marketing", "audience research"], "us"
mavera = OpenAI(base_url=MB, api_key=MV)

resp = requests.get("https://api.ahrefs.com/v3/keywords-explorer/keyword-ideas", headers=AH_H,
    params={"keywords": ",".join(SEEDS), "country": COUNTRY, "limit": 200,
            "order_by": "volume:desc",
            "select": "keyword,volume,keyword_difficulty,cpc,parent_topic"})
resp.raise_for_status()
ideas = resp.json().get("keywords", [])

clusters = defaultdict(list)
for kw in ideas:
    clusters[kw.get("parent_topic", kw["keyword"].split()[0])].append(kw)
top_clusters = sorted(clusters.items(), key=lambda c: sum(k["volume"] for k in c[1]),
                       reverse=True)[:8]
print(f"Keywords: {len(ideas)} | Clusters: {len(clusters)} | Top {len(top_clusters)}\n")

bv = requests.post(f"{MB}/brand-voices", headers=MH, json={
    "name": "SEO Content Voice",
    "description": "Clear, authoritative, data-backed. For practitioners, not beginners.",
    "samples": ["73% of teams skip validation. Here's the framework that fixes that.",
                "1,200 top pages analyzed. Pattern: data-first, opinion-second."],
}).json()

for topic, kws in top_clusters:
    kws_sorted = sorted(kws, key=lambda k: k["volume"], reverse=True)
    total_vol = sum(k["volume"] for k in kws)
    avg_kd = sum(k.get("keyword_difficulty", 0) for k in kws) // max(len(kws), 1)
    kw_list = "\n".join(f"- {k['keyword']} (vol: {k['volume']}, KD: {k.get('keyword_difficulty', 0)}, "
                        f"CPC: ${k.get('cpc', 0):.2f})" for k in kws_sorted[:12])
    gen = requests.post(f"{MB}/generations", headers=MH, json={
        "prompt": f"SEO plan for '{topic}'\nVol: {total_vol} | KD: {avg_kd} | KWs: {len(kws)}\n\n"
                  f"KEYWORDS:\n{kw_list}\n\nGenerate: 1) Title (60ch) 2) Meta (155ch) "
                  f"3) Primary + 4 secondary KWs 4) H2/H3 outline 5) Word count "
                  f"6) SERP features 7) Internal links",
        "brand_voice_id": bv["id"],
    }).json()
    print(f"=== {topic} ({len(kws)} kws, {total_vol:,} vol, KD {avg_kd}) ===")
    print(gen.get("output", gen.get("content", ""))[:600], "\n")
    time.sleep(1)

chat = mavera.responses.create(model="mave", input=[{"role": "user", "content":
    f"Summarize: {len(top_clusters)} clusters, "
    f"{sum(len(kws) for _, kws in top_clusters)} keywords, "
    f"{sum(sum(k['volume'] for k in kws) for _, kws in top_clusters):,} volume. "
    f"2-week execution schedule by volume and difficulty."}])
print("=== Execution Schedule ===\n" + chat.output[0].content[0].text)

Example Output

{
  "total_keywords": 200,
  "clusters_processed": 8,
  "sample_cluster": {
    "topic": "content marketing strategy",
    "keywords": 18, "total_volume": 24600, "avg_difficulty": 42,
    "title": "Content Marketing Strategy: A Data-Driven Framework for 2026",
    "primary_keyword": "content marketing strategy",
    "word_count": 3200,
    "outline": ["Why Most Strategies Fail", "The Data-First Framework", "Audience Validation", "Topic Clustering", "Production Calendar", "Measurement"]
  },
  "schedule": {
    "week_1": ["content marketing strategy (24.6k vol)", "audience research tools (12.1k vol)"],
    "week_2": ["content testing (8.4k vol)", "brand voice AI (6.2k vol)"]
  }
}

Error Handling

The seed keywords may be too specific or the country database too small. Try broader seeds or a larger market (us, uk, de). Ahrefs requires a minimum of 50 units per request even for empty results.
Not all keywords have a parent_topic assigned in Ahrefs’ database. The code falls back to the first word of the keyword — for production, implement proper NLP-based clustering or use Ahrefs’ built-in clustering when available.