AGENT 01 / AI SEO CONTENT AGENT / v1.0 / SADDAM ADIL
⚠ ADD API KEYS IN CONFIG TAB FIRSTn8n-compatible

AI-Powered SEO Content Agent

Keyword in → SERP research → Gap analysis → GEO-optimised article → WordPress draft + Slack alert
All 7 steps run automatically. Add API keys in Config tab before running.

7
Pipeline Steps
60%
Time Saved
€0.30
Cost / Article
GEO
AI-Search Ready
01
Input
idle
02
SERP Research
idle
03
Gap Analysis
idle
04
Outline
idle
05
Draft Article
idle
06
WP Push
idle
07
Notify
idle
▶ Run Agent
⚙ Config & API Keys
{ } Full Code
📄 Output
Keyword & Parameters
Estimated: 45–90 seconds with real APIs
⚠ API keys saved to browser localStorage only — never transmitted anywhere by this app.
Keys marked REQUIRED must be set before the agent can run. OPTIONAL keys enable extra features.
API Keys
PERPLEXITY_API_KEYREQUIRED
platform.perplexity.ai → API → Generate Key · ~USD 5/month
OPENAI_API_KEYREQUIRED
platform.openai.com → API Keys → Create key · ~USD 0.02/article
WORDPRESS_SITE_URLOPTIONAL
Your WordPress site URL — enables auto-publishing drafts
WORDPRESS_USERNAMEOPTIONAL
WordPress admin username
WORDPRESS_APP_PASSWORDOPTIONAL
WP Admin → Users → Application Passwords → Generate
SLACK_WEBHOOK_URLOPTIONAL
api.slack.com → Apps → Incoming Webhooks → Add Webhook
How to get each API key

Perplexity API (REQUIRED)

1. Go to platform.perplexity.ai
2. Sign up / log in
3. Click "API" in left sidebar
4. Click "Generate New API Key"
5. Copy the key (starts with pplx-)
Cost: Pay-as-you-go. ~$5/month for 100 articles

OpenAI API (REQUIRED)

1. Go to platform.openai.com
2. Sign up / log in
3. Left sidebar → "API Keys"
4. Click "+ Create new secret key"
5. Copy the key (starts with sk-)
Cost: GPT-4o-mini ~$0.002/article. GPT-4o ~$0.02/article

WordPress App Password (OPTIONAL)

1. Log into WordPress admin panel
2. Go to Users → Your Profile
3. Scroll to "Application Passwords"
4. New name: "SEO Agent" → Add
5. Copy the generated password
Cost: Free (your own WordPress)

Slack Webhook (OPTIONAL)

1. Go to api.slack.com/apps
2. Create New App → From Scratch
3. Enable "Incoming Webhooks"
4. Click "Add New Webhook to Workspace"
5. Pick channel → copy the URL
Cost: Free
This is the complete production code for all 7 pipeline steps.
Replace every ADD_YOUR_XXX_KEY_HERE placeholder with your actual key, or use the Config tab (keys auto-load here too).
Step 1 — Input Validation
// STEP 1: Input Validation
// n8n: Use a "Code" node as first node after Webhook trigger

const validateInput = (params) => {
  const { keyword, wordCount, lang, audience, contentType, voice } = params;
  if (!keyword || keyword.trim().length < 3) throw new Error("Keyword must be at least 3 characters");
  return {
    keyword:     keyword.trim().toLowerCase(),
    wordCount:   parseInt(wordCount) || 1500,
    lang:        lang || "en",
    audience:    audience || "business professionals",
    contentType: contentType || "blog",
    voice:       voice || "professional",
    timestamp:   new Date().toISOString(),
    runId:       Math.random().toString(36).slice(2, 10)
  };
};
Step 2 — SERP Research (Perplexity API)
// STEP 2: SERP Research via Perplexity
// n8n: HTTP Request node
//   URL: https://api.perplexity.ai/chat/completions
//   Method: POST
//   Auth: Header Auth  →  Authorization: Bearer YOUR_KEY

const PERPLEXITY_API_KEY = "ADD_YOUR_PERPLEXITY_KEY_HERE"; // ← REPLACE THIS

const serpResearch = async (keyword, lang) => {
  const res = await fetch("https://api.perplexity.ai/chat/completions", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${PERPLEXITY_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "llama-3.1-sonar-large-128k-online",
      messages: [
        { role: "system", content: "You are an expert SEO research analyst. Return ONLY valid JSON." },
        { role: "user", content: `Research top 10 ranking pages for keyword: "${keyword}"
Language: ${lang}. Return JSON:
{
  "topPages": [{ "url":"", "title":"", "keyPoints":[], "wordCount":0 }],
  "commonHeadings": [],
  "averageWordCount": 0,
  "topEntities": [],
  "relatedQuestions": [],
  "searchIntent": "informational|commercial|transactional"
}` }
      ],
      temperature: 0.1,
      return_related_questions: true
    })
  });
  const data = await res.json();
  return JSON.parse(data.choices[0].message.content);
};
Step 3 — Gap Analysis (OpenAI)
// STEP 3: Content Gap Analysis via OpenAI
// n8n: HTTP Request node
//   URL: https://api.openai.com/v1/chat/completions
//   Auth: Header Auth  →  Authorization: Bearer YOUR_KEY

const OPENAI_API_KEY = "ADD_YOUR_OPENAI_KEY_HERE"; // ← REPLACE THIS

const gapAnalysis = async (serpData, keyword, audience) => {
  const res = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: { "Authorization": `Bearer ${OPENAI_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "gpt-4o-mini",
      response_format: { type: "json_object" },
      messages: [
        { role: "system", content: "Expert content strategist. Return ONLY valid JSON." },
        { role: "user", content: `Keyword: "${keyword}" | Audience: ${audience}
SERP Data: ${JSON.stringify(serpData)}

Find content gaps. Return:
{
  "gaps": ["missing angle 1", "missing angle 2"],
  "uniqueAngles": ["competitive differentiator 1"],
  "missingEntities": ["entity not covered by competitors"],
  "geoOpportunities": ["direct answer opportunity for AI search"],
  "recommendedWordCount": 1600
}` }
      ],
      temperature: 0.3
    })
  });
  const d = await res.json();
  return JSON.parse(d.choices[0].message.content);
};
Step 4 — Outline Generator (OpenAI)
// STEP 4: Structured Article Outline
// Same OpenAI key used here

const generateOutline = async (keyword, gapData, params) => {
  const res = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: { "Authorization": `Bearer ${OPENAI_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "gpt-4o-mini",
      response_format: { type: "json_object" },
      messages: [
        { role: "system", content: "Expert SEO content architect. Return ONLY valid JSON." },
        { role: "user", content: `Create ${params.wordCount}-word ${params.contentType} outline for: "${keyword}"
Audience: ${params.audience} | Voice: ${params.voice}
Gaps to fill: ${JSON.stringify(gapData.gaps)}
GEO opportunities: ${JSON.stringify(gapData.geoOpportunities)}

Return:
{
  "title": "SEO-optimised H1 title",
  "metaDescription": "155-char meta description",
  "slug": "url-friendly-slug",
  "sections": [{ "h2": "heading", "wordCount": 300, "keyPoints": [], "geoTarget": true }],
  "faqQuestions": ["Q1","Q2","Q3"]
}` }
      ],
      temperature: 0.4
    })
  });
  const d = await res.json();
  return JSON.parse(d.choices[0].message.content);
};
Step 5 — Full Article Draft (OpenAI GPT-4o)
// STEP 5: Full Article Draft
// Uses GPT-4o (not mini) for higher quality output
// This is the most expensive step: ~$0.015 per article

const draftArticle = async (outline, keyword, params) => {
  const langRule = params.lang === "de"
    ? "Write in formal German. Use Sie-form. No anglicisms."
    : params.lang === "ar"
    ? "Write in Modern Standard Arabic (MSA). RTL layout."
    : "Write in clear, direct professional English.";

  const res = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: { "Authorization": `Bearer ${OPENAI_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "gpt-4o",
      max_tokens: 4000,
      messages: [
        { role: "system", content: `World-class B2B content writer.
Rules:
- ${langRule}
- Voice: ${params.voice}
- Target exactly ${params.wordCount} words
- GEO: write direct concise answers AI engines will cite
- Use markdown: ## H2, ### H3, **bold** for key terms
- Include real statistics with years (e.g. "According to McKinsey 2024...")
- FAQ section at end with schema-ready Q and A format` },
        { role: "user", content: `Write full article for keyword: "${keyword}"
Title: ${outline.title}
Audience: ${params.audience}

Sections:
${outline.sections.map((s,i) =>
  `${i+1}. ${s.h2} (~${s.wordCount} words): ${s.keyPoints?.join(", ")}`).join("\n")}

FAQ questions: ${outline.faqQuestions?.join(" | ")}` }
      ],
      temperature: 0.7
    })
  });
  const d = await res.json();
  return d.choices[0].message.content;
};
Step 6 — WordPress Push (REST API)
// STEP 6: Push to WordPress as Draft
// OPTIONAL - only runs if WP credentials are configured
// n8n: HTTP Request node
//   URL: https://YOUR_SITE/wp-json/wp/v2/posts
//   Auth: Basic Auth → username + app password

const WP_URL      = "ADD_YOUR_WORDPRESS_URL_HERE";      // ← e.g. https://saddamadil.in
const WP_USERNAME = "ADD_YOUR_WORDPRESS_USERNAME_HERE";  // ← e.g. admin
const WP_APP_PASS = "ADD_YOUR_WP_APP_PASSWORD_HERE";     // ← xxxx xxxx xxxx xxxx

const pushToWordPress = async (outline, articleMarkdown) => {
  if (!WP_URL || WP_URL.includes("ADD_YOUR")) {
    return { skipped: true, reason: "WordPress credentials not configured" };
  }
  const credentials = btoa(`${WP_USERNAME}:${WP_APP_PASS}`);
  const res = await fetch(`${WP_URL}/wp-json/wp/v2/posts`, {
    method: "POST",
    headers: {
      "Authorization": `Basic ${credentials}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      title:   outline.title,
      content: articleMarkdown,
      slug:    outline.slug,
      excerpt: outline.metaDescription,
      status:  "draft",   // Always draft - human reviews before publishing
      meta: { _yoast_wpseo_metadesc: outline.metaDescription }
    })
  });
  const post = await res.json();
  return { postId: post.id, editUrl: `${WP_URL}/wp-admin/post.php?post=${post.id}&action=edit` };
};
Step 7 — Slack Notification
// STEP 7: Slack Notification
// OPTIONAL - skipped if webhook not configured
// n8n: HTTP Request node → POST to your webhook URL

const SLACK_WEBHOOK = "ADD_YOUR_SLACK_WEBHOOK_URL_HERE"; // ← REPLACE

const notifySlack = async (outline, wpResult, params, runId) => {
  if (!SLACK_WEBHOOK || SLACK_WEBHOOK.includes("ADD_YOUR")) return { skipped: true };
  const editLink = wpResult?.editUrl || "(WordPress not configured)";
  await fetch(SLACK_WEBHOOK, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      blocks: [
        { type: "header", text: { type: "plain_text", text: "SEO Article Draft Ready" } },
        { type: "section", fields: [
          { type: "mrkdwn", text: `*Title:*\n${outline.title}` },
          { type: "mrkdwn", text: `*Keyword:*\n${params.keyword}` },
          { type: "mrkdwn", text: `*Language:*\n${params.lang.toUpperCase()}` },
          { type: "mrkdwn", text: `*Run ID:*\n${runId}` }
        ]},
        { type: "section", text: { type: "mrkdwn", text: `*Edit in WordPress:*\n${editLink}` } },
        { type: "section", text: { type: "mrkdwn", text: "Review, edit, and publish when ready." } }
      ]
    })
  });
  return { sent: true };
};
Master Runner — Connects All 7 Steps
// MASTER RUNNER: Chains all 7 steps together
// In n8n: Each step above is a separate node connected in sequence
// For standalone Node.js: copy all functions above + this runner into one file

const runSEOAgent = async (rawParams) => {
  let params, serpData, gapData, outline, article, wpResult;
  try {
    // Step 1: Validate
    params = validateInput(rawParams);
    console.log(`[01] Input validated: "${params.keyword}"`);

    // Step 2: SERP Research
    serpData = await serpResearch(params.keyword, params.lang);
    console.log(`[02] Found ${serpData.topPages?.length} top pages`);

    // Step 3: Gap Analysis
    gapData = await gapAnalysis(serpData, params.keyword, params.audience);
    console.log(`[03] Found ${gapData.gaps?.length} content gaps`);

    // Step 4: Outline
    outline = await generateOutline(params.keyword, gapData, params);
    console.log(`[04] Outline: "${outline.title}" | ${outline.sections?.length} sections`);

    // Step 5: Draft Article
    article = await draftArticle(outline, params.keyword, params);
    console.log(`[05] Draft: ${article.split(" ").length} words`);

    // Step 6: WordPress (optional)
    wpResult = await pushToWordPress(outline, article);
    console.log(`[06] WordPress: ${wpResult.skipped ? "skipped" : `post #${wpResult.postId}`}`);

    // Step 7: Notify (optional)
    await notifySlack(outline, wpResult, params, params.runId);
    console.log("[07] Done.");

    return { success: true, outline, article, wpResult };
  } catch (err) {
    console.error("Agent error:", err.message);
    return { success: false, error: err.message };
  }
};

// Run it:
runSEOAgent({
  keyword:     "epoxy resins for automotive composites",
  lang:        "en",
  wordCount:   1500,
  contentType: "whitepaper",
  audience:    "industrial procurement managers in Germany",
  voice:       "technical"
}).then(console.log);
Generated Article Draft
No output yet. Run the agent first.