API Documentation

Programmatic access to SEO intelligence, AI visibility scores, content analysis, and automated optimizations.

22 Endpoints REST API MCP Server Bearer Auth
Base URL: https://seojuice.com/api/v2

Authentication

All API requests require a Bearer token in the Authorization header. Find your API token in Dashboard → Settings → API.

Every request is scoped to your organization. You can only access websites that belong to your account.

curl
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://seojuice.com/api/v2/websites/
Python
import requests

resp = requests.get(
    "https://seojuice.com/api/v2/websites/",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"},
)
data = resp.json()
JavaScript
const resp = await fetch("https://seojuice.com/api/v2/websites/", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await resp.json();

If authentication fails, you receive a 401 response:

Response — 401
{
  "error": "invalid_or_missing_token",
  "message": "Authorization header must be provided with format: Bearer <token>"
}

Pagination

List endpoints return paginated results. Control pagination with query parameters:

ParameterTypeDefaultDescription
pageinteger1Page number (1-indexed)
page_sizeinteger10-20Results per page (max 100)

Every paginated response includes a pagination object:

Pagination object
{
  "pagination": {
    "page": 1,
    "page_size": 10,
    "total_count": 247,
    "total_pages": 25
  },
  "results": [...]
}

Error Handling

All errors return a consistent JSON structure:

Error response format
{
  "error": "not_found",
  "message": "Website not found"
}
Error CodeHTTP StatusDescription
invalid_or_missing_token401No Authorization header or wrong scheme
invalid_token401Token not found in database
forbidden403Plan does not include this feature
not_found404Resource not found
invalid_pagination400Non-integer or out-of-range pagination params
missing_parameter400Required query parameter absent
invalid_request400Invalid JSON body
rate_limit_exceeded429Hourly rate limit exceeded

Websites & Pages

GET /api/v2/websites/

List all websites in your organization. Returns all websites in a single array (no pagination).

curl
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://seojuice.com/api/v2/websites/
Show response
{
  "results": [
    {"domain": "example.com", "created_at": "2024-01-15T10:00:00+00:00"},
    {"domain": "blog.example.com", "created_at": "2024-03-22T14:30:00+00:00"}
  ]
}
GET /api/v2/websites/{domain}/

Get a single website with its latest report and key performance indicators.

Python
resp = requests.get(
    "https://seojuice.com/api/v2/websites/example.com/",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"},
)
website = resp.json()
Show response
{
  "domain": "example.com",
  "created_at": "2024-01-15T10:00:00+00:00",
  "last_processed_at": "2024-02-10T08:30:00+00:00",
  "report": {
    "created_at": "2024-02-10T08:30:00+00:00",
    "data": {}
  },
  "kpis": {
    "total_links": 1842,
    "total_pages": 234,
    "total_keywords": 567
  }
}
GET /api/v2/websites/{domain}/pages/

List all pages for a website. Each page includes its internal links. Paginated.

ParameterTypeRequiredDescription
pageintegerNoPage number (default: 1)
page_sizeintegerNoResults per page (default: 10, max: 100)
Show response
{
  "pagination": {"page": 1, "page_size": 20, "total_count": 234, "total_pages": 12},
  "results": [
    {
      "url": "https://example.com/blog/seo-tips",
      "created_at": "2024-01-20T12:00:00+00:00",
      "last_processed_at": "2024-02-08T14:00:00+00:00",
      "links": [
        {
          "page_from": "https://example.com/blog/seo-tips",
          "page_to": "https://example.com/about",
          "keyword": "learn more about us",
          "created_at": "2024-01-20T12:00:00+00:00",
          "impressions": 42
        }
      ]
    }
  ]
}
GET /api/v2/websites/{domain}/pages/{page_id}/

Get a single page with its internal links.

curl
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://seojuice.com/api/v2/websites/example.com/pages/4521/

Intelligence

GET /api/v2/websites/{domain}/intelligence/

Composite SEO intelligence summary including SEO score, AISO score, page counts, cluster counts, link metrics, and content gap count.

ParameterTypeRequiredDescription
periodstringNo7d, 30d, or 90d (default: 30d)
include_historybooleanNoInclude time-series snapshot data
include_trendsbooleanNoInclude period-over-period deltas
Python
resp = requests.get(
    "https://seojuice.com/api/v2/websites/example.com/intelligence/",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"},
    params={"period": "30d", "include_trends": "true"},
)
intel = resp.json()
Show response
{
  "domain": "example.com",
  "seo_score": 78,
  "aiso_score": 62,
  "total_pages": 234,
  "total_clusters": 12,
  "total_internal_links": 1842,
  "total_seojuice_links": 3,
  "orphan_pages": 17,
  "content_gaps": 45,
  "last_crawled_at": "2024-02-10T08:30:00+00:00",
  "trends": {
    "seo_score_change": 6.0,
    "pages_change": 12,
    "clicks_change_pct": 23.3,
    "impressions_change_pct": 13.3
  }
}

Topology

GET /api/v2/websites/{domain}/topology/

Internal link graph analysis. Returns orphan pages, link depth distribution, and most-linked pages.

Show response
{
  "total_pages": 234,
  "total_internal_links": 1842,
  "orphan_pages_count": 17,
  "orphan_pages": [{"url": "https://example.com/orphan-page", "title": "Orphan Page Title"}],
  "link_depth_distribution": {"0": 1, "1": 23, "2": 89, "3": 64, "4+": 57},
  "avg_links_per_page": 7.9,
  "most_linked_pages": [{"url": "https://example.com/", "title": "Home", "incoming_links": 234}]
}

Page Speed

GET /api/v2/websites/{domain}/pagespeed/

Core Web Vitals and Lighthouse scores for a specific page URL.

ParameterTypeRequiredDescription
urlstringYesFull URL of the page to check
Show response
{
  "url": "https://example.com/blog/seo-tips",
  "loading_time": 1.24,
  "core_web_vitals": {"lcp": 2.1, "cls": 0.05, "fid": 12, "inp": 45, "fcp": 1.3, "ttfb": 0.4},
  "scores": {"performance": 87, "accessibility": 94, "best_practices": 92, "seo": 96},
  "resource_sizes": {"total_kb": 1240, "js_kb": 480, "css_kb": 120, "image_kb": 580},
  "measured_at": "2024-02-10T08:30:00+00:00"
}

Clusters

GET /api/v2/websites/{domain}/clusters/

List topic clusters identified by semantic analysis. Clusters group related pages by content similarity. Paginated.

Show response
{
  "pagination": {"page": 1, "page_size": 20, "total_count": 12, "total_pages": 1},
  "results": [{
    "id": 42, "name": "SEO Guides", "slug": "seo-guides", "page_count": 18,
    "avg_relevance_score": 0.85, "total_clicks": 4200, "avg_position": 12.3,
    "traffic_trend": "growing", "internal_link_density": 0.73, "orphan_pages": 2
  }]
}
GET /api/v2/websites/{domain}/clusters/{cluster_id}/

Cluster detail with top keywords, time-series performance data, and quality history.

Content Gaps

GET /api/v2/websites/{domain}/content-gaps/

Content opportunities identified by AI analysis of competitor rankings, search volumes, and AI citation gaps.

ParameterTypeRequiredDescription
categorystringNoFilter by content category
intentstringNoinformational, transactional, navigational, or commercial
Show response
{
  "pagination": {"page": 1, "page_size": 20, "total_count": 45, "total_pages": 3},
  "results": [{
    "id": 101, "page_name": "How to Build Internal Links", "category": "Technical SEO",
    "intent": "informational", "seo_potential": 87, "total_search_volume": 12400,
    "keywords": [{"keyword": "internal linking strategy", "rank_potential": 3, "color": "green"}],
    "aiso_driven": false, "is_generated": false, "has_potential_candidate": true
  }]
}

Competitors

GET /api/v2/websites/{domain}/competitors/

Competitor analysis with keyword overlap, traffic estimates, and ranking comparisons.

ParameterTypeRequiredDescription
include_trendsbooleanNoInclude period-over-period trend data
Show response
{
  "pagination": {"page": 1, "page_size": 20, "total_count": 8, "total_pages": 1},
  "results": [{
    "id": 55, "domain": "competitor.com", "score": 82, "intersections": 145,
    "estimated_traffic": 52000, "content_gaps_count": 23, "avg_position": 15.4,
    "top_keywords": [{"keyword": "seo tools", "your_position": 8, "their_position": 3, "volume": 33100}],
    "trends": {"intersections_change": 12, "traffic_change_pct": 5.3, "keywords_change": 8}
  }]
}

AISO — AI Search Visibility

GET /api/v2/websites/{domain}/aiso/

AI Search Optimization score. Tracks how often your site is cited by ChatGPT, Claude, Perplexity, and Gemini. The composite score is built from five sub-dimensions: visibility, sentiment, position, coverage, and competitive standing.

ParameterTypeRequiredDescription
periodstringNo7d, 30d, or 90d (default: 30d)
include_historybooleanNoInclude 12-month snapshot history
Show response
{
  "aiso_score": 62,
  "sub_scores": {"visibility": 70, "sentiment": 65, "position": 58, "coverage": 55, "competitive": 62},
  "total_mentions": 284, "your_mentions": 48, "avg_position": 3.2, "positive_rate": 0.79,
  "providers": {},
  "history": {
    "months": ["2024-03", "2024-04", "2024-05"],
    "aiso_scores": [55, 58, 62],
    "total_mentions": [210, 245, 284],
    "your_mentions": [35, 41, 48]
  }
}

Semantic Search

GET /api/v2/websites/{domain}/similar/

Find semantically similar pages using vector embeddings. Powered by Pinecone. Results cached for 1 hour.

ParameterTypeRequiredDescription
urlstringYesFull URL of the source page
limitintegerNoNumber of results (default: 10, max: 50)
Show response
{
  "source": {"url": "https://example.com/blog/seo-tips", "title": "10 SEO Tips for 2024"},
  "similar_pages": [
    {"url": "https://example.com/blog/link-building", "title": "Link Building Guide", "similarity": 0.923, "cluster": "SEO Guides"}
  ]
}

Page Analysis

POST /api/v2/websites/{domain}/analyze/

Trigger an asynchronous on-demand analysis for a page. Returns immediately with an analysis ID to poll.

curl
curl -X POST \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/blog/seo-tips"}' \
  https://seojuice.com/api/v2/websites/example.com/analyze/
Show response — 202 Accepted
{
  "analysis_id": "abc123XYZ_randomtoken",
  "url": "https://example.com/blog/seo-tips",
  "status": "queued",
  "status_url": "/api/v2/websites/example.com/analyze/abc123XYZ_randomtoken/",
  "estimated_time_seconds": 30
}
GET /api/v2/websites/{domain}/analyze/{analysis_id}/

Poll the status of an async page analysis. Status: queuedin_progresscompleted | failed

Show response — completed
{
  "analysis_id": "abc123XYZ_randomtoken", "status": "completed",
  "url": "https://example.com/blog/seo-tips", "page_id": 4521,
  "cluster": {"id": 42, "name": "SEO Guides"}, "seo_score": 78,
  "is_orphan": false, "depth_from_homepage": 2,
  "recommended_links": [], "recommended_meta": {},
  "recommended_structured_data": {}, "aiso_visibility": 0,
  "completed_at": "2024-02-10T08:30:45+00:00"
}

Reports

Plan requirement: Report endpoints require a Startup plan or above. Free tier organizations receive a 403 Forbidden response.

GET /api/v2/websites/{domain}/reports/

List all generated reports for a website. Paginated, ordered by creation date descending.

Show response
{
  "pagination": {"page": 1, "page_size": 12, "total_count": 5, "total_pages": 1},
  "results": [{
    "id": 7, "type": "last_month", "type_display": "Last Month",
    "status": "completed", "date": "2024-01-01", "end_date": "2024-01-31",
    "created_at": "2024-02-01T08:00:00+00:00", "has_pdf": true
  }]
}
POST /api/v2/websites/{domain}/reports/

Generate a new SEO report. Returns immediately — poll the status URL for completion.

Body fieldTypeRequiredDescription
typestringYesthis_month, last_month, this_week, or last_week
curl
curl -X POST \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"type": "last_month"}' \
  https://seojuice.com/api/v2/websites/example.com/reports/
Show response — 202 Accepted
{
  "report_id": 8, "status": "in_progress",
  "status_url": "/api/v2/websites/example.com/reports/8/",
  "task_id": "celery-task-uuid-here"
}
GET /api/v2/websites/{domain}/reports/{report_id}/

Report detail with full summary data and PDF download URL.

Show response
{
  "id": 7, "type": "last_month", "type_display": "Last Month",
  "status": "completed", "date": "2024-01-01", "end_date": "2024-01-31",
  "created_at": "2024-02-01T08:00:00+00:00", "has_pdf": true,
  "summary": {}, "data": {},
  "updated_at": "2024-02-01T08:05:00+00:00",
  "pdf_url": "/api/v2/websites/example.com/reports/7/pdf/"
}
GET /api/v2/websites/{domain}/reports/{report_id}/pdf/

Download the report as a PDF file. Returns binary application/pdf.

curl
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -o report.pdf \
  https://seojuice.com/api/v2/websites/example.com/reports/7/pdf/

Google Business Profile

GET /api/v2/websites/{domain}/gbp/locations/

List all active Google Business Profile locations linked to the website.

Show response
{
  "results": [{
    "id": 3, "location_id": "accounts/123/locations/456",
    "name": "SEOJuice HQ", "address": "123 Main St, San Francisco, CA",
    "phone": "+1-555-555-5555", "average_rating": 4.7, "total_reviews": 89,
    "last_fetched_at": "2024-02-10T06:00:00+00:00"
  }]
}
GET /api/v2/websites/{domain}/gbp/reviews/

Paginated list of Google Business Profile reviews with filtering options. Includes AI-generated reply suggestions.

ParameterTypeRequiredDescription
ratingintegerNoFilter by star rating (1-5)
sentimentstringNopositive, negative, or neutral
needs_attentionbooleanNoOnly unanswered or flagged reviews
location_idintegerNoFilter to a specific location
Show response
{
  "results": [{
    "id": 201, "review_id": "AHEIO2fabc123xyz",
    "location_name": "SEOJuice HQ", "author_name": "Jane Smith",
    "rating": 5, "comment": "Great service, very helpful!",
    "reply": null, "reply_suggestion": "Thank you for your kind words, Jane!",
    "sentiment": "positive", "needs_attention": false, "auto_replied": false,
    "published_at": "2024-02-08T14:00:00+00:00", "reply_posted_at": null
  }],
  "pagination": {"page": 1, "page_size": 20, "total_count": 89, "total_pages": 5}
}
POST /api/v2/websites/{domain}/gbp/reviews/{review_id}/reply/

Post a reply to a Google Business Profile review. Published directly to Google.

Body fieldTypeRequiredDescription
reply_textstringYesThe reply text to publish
curl
curl -X POST \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reply_text": "Thank you for your kind review, Jane!"}' \
  https://seojuice.com/api/v2/websites/example.com/gbp/reviews/201/reply/
Show response
{"success": true, "review_id": 201, "reply": "Thank you for your kind review, Jane!"}

MCP Server

SEOJuice provides a Model Context Protocol (MCP) server for AI assistant integration. Connect Claude Desktop, Cursor, Windsurf, or any MCP-compatible client to access SEO intelligence tools directly from your AI workflow.

Endpoint: https://seojuice.com/mcp

Transport: Streamable HTTP (JSON-RPC 2.0)

Authentication: OAuth 2.0 + PKCE (automatic via discovery)

Quick Setup

Add SEOJuice to your MCP client configuration. OAuth authentication is handled automatically — your client will open a browser for login on first connection.

Claude Desktop — claude_desktop_config.json
{
  "mcpServers": {
    "seojuice": {
      "url": "https://seojuice.com/mcp",
      "transport": "streamable-http"
    }
  }
}

Available Tools

All tools are read-only and scoped to your organization. Use list_websites first to discover available domains.

Tool Description Parameters
list_websites List all tracked websites none
get_website_detail Website KPIs (pages, links, keywords) domain
list_pages Paginated pages with internal links domain, page, page_size
get_page_detail Single page detail with links domain, page_id
get_intelligence_summary SEO score, AISO score, trends, history domain, period, include_history, include_trends
get_site_topology Link graph, orphan pages, depth distribution domain
get_pagespeed Core Web Vitals and Lighthouse scores domain, url
list_clusters Content clusters with page counts domain, page, page_size
get_cluster_detail Single cluster with time series domain, cluster_id
list_content_gaps Content gap opportunities domain, category, intent, page, page_size
list_competitors Competitor keyword overlap and trends domain, include_trends, page, page_size

OAuth Discovery

MCP clients discover OAuth configuration automatically. These endpoints are public (no authentication required):

Endpoint Purpose
GET /.well-known/oauth-protected-resource RFC 9728 — resource metadata, authorization server URLs, supported scopes
GET /.well-known/oauth-authorization-server RFC 8414 — authorization, token, and JWKS endpoints

Security

  • All data is scoped to the authenticated user's organization
  • All tools are read-only — no data modifications possible
  • OAuth 2.0 + PKCE ensures secure token exchange without client secrets
  • JWT tokens are validated against Auth0 JWKS with signature verification

Webhooks

Receive real-time notifications when events happen in your SEOJuice account. Configure webhook endpoints in Dashboard → Settings → Webhooks.

Delivery details: Webhooks are sent as POST requests with a JSON body. Failed deliveries are retried up to 3 times at 60 s, 300 s, and 1800 s intervals.

Request Headers

HeaderDescription
X-SEOJuice-EventEvent type, e.g. report.completed
X-SEOJuice-SignatureHMAC-SHA256 hex digest of the raw request body
X-SEOJuice-Delivery-IDUnique UUID for this delivery attempt
Content-Typeapplication/json

Signature Verification

Every webhook includes an X-SEOJuice-Signature header. Compute the HMAC-SHA256 of the raw request body using your webhook secret and compare:

Python
import hmac
import hashlib

def verify_signature(payload_body: bytes, secret: str, signature: str) -> bool:
    expected = hmac.new(
        secret.encode(), payload_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
JavaScript
const crypto = require("crypto");

function verifySignature(payloadBody, secret, signature) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payloadBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

Event Reference

EventTriggerKey Payload Fields
report.completedReport generation finishesreport_id, type, json_url, pdf_url
report.failedReport generation failsreport_id, error_message
cluster.updatedCluster pages changecluster_id, name, page_count_change
content_gap.foundNew content gap detectedgap_id, page_name, seo_potential
aiso.score_changedAISO score shifts ≥10 pointsold_score, new_score, change
gbp.review_receivedNew Google Business reviewreview_id, rating, author, comment, sentiment
gbp.auto_reply_postedAutomatic reply sentreview_id, rating, reply

Example Payload

Full payload for report.completed:

JSON payload
{
  "event": "report.completed",
  "delivery_id": "d4e5f6a7-b8c9-0123-4567-89abcdef0123",
  "timestamp": "2024-02-01T08:05:00+00:00",
  "data": {
    "domain": "example.com",
    "report_id": 7,
    "type": "last_month",
    "type_display": "Last Month",
    "date": "2024-01-01",
    "end_date": "2024-01-31",
    "json_url": "/api/v2/websites/example.com/reports/7/",
    "pdf_url": "/api/v2/websites/example.com/reports/7/pdf/"
  }
}

Integration Examples

Python Client

A minimal client that wraps all common operations. Drop this into your project and call methods directly.

Python
import time
import requests

class SEOJuiceClient:
    def __init__(self, token: str, base_url: str = "https://seojuice.com/api/v2"):
        self.base_url = base_url.rstrip("/")
        self.session = requests.Session()
        self.session.headers["Authorization"] = f"Bearer {token}"

    def _request(self, method: str, path: str, **kwargs):
        resp = self.session.request(method, f"{self.base_url}{path}", **kwargs)
        resp.raise_for_status()
        return resp.json()

    def get_websites(self) -> list:
        return self._request("GET", "/websites/")["results"]

    def get_intelligence(self, domain: str, period: str = "30d") -> dict:
        return self._request("GET", f"/websites/{domain}/intelligence/",
                             params={"period": period, "include_trends": "true"})

    def get_content_gaps(self, domain: str, page: int = 1) -> dict:
        return self._request("GET", f"/websites/{domain}/content-gaps/",
                             params={"page": page})

    def get_aiso(self, domain: str, period: str = "30d") -> dict:
        return self._request("GET", f"/websites/{domain}/aiso/",
                             params={"period": period, "include_history": "true"})

    def get_competitors(self, domain: str) -> dict:
        return self._request("GET", f"/websites/{domain}/competitors/",
                             params={"include_trends": "true"})

    def analyze_page(self, domain: str, url: str, timeout: int = 120) -> dict:
        """Trigger analysis and poll until complete."""
        result = self._request("POST", f"/websites/{domain}/analyze/",
                               json={"url": url})
        status_url = result["status_url"]
        deadline = time.time() + timeout
        while time.time() < deadline:
            status = self._request("GET", status_url)
            if status["status"] == "completed":
                return status
            if status["status"] == "failed":
                raise RuntimeError(f"Analysis failed: {status}")
            time.sleep(5)
        raise TimeoutError("Analysis did not complete in time")


# Usage
client = SEOJuiceClient("YOUR_API_TOKEN")
sites = client.get_websites()
intel = client.get_intelligence("example.com")
print(f"SEO Score: {intel['seo_score']}, AISO: {intel['aiso_score']}")

JavaScript Client

ES module client using the Fetch API. Works in Node.js 18+ and modern browsers.

JavaScript
class SEOJuiceClient {
  constructor(token, baseUrl = "https://seojuice.com/api/v2") {
    this.baseUrl = baseUrl.replace(/\/$/, "");
    this.headers = {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    };
  }

  async _request(method, path, opts = {}) {
    const url = new URL(`${this.baseUrl}${path}`);
    if (opts.params) {
      Object.entries(opts.params).forEach(([k, v]) => url.searchParams.set(k, v));
    }
    const resp = await fetch(url, {
      method,
      headers: this.headers,
      body: opts.body ? JSON.stringify(opts.body) : undefined,
    });
    if (!resp.ok) throw new Error(`API error ${resp.status}: ${await resp.text()}`);
    return resp.json();
  }

  getWebsites() { return this._request("GET", "/websites/"); }
  getIntelligence(domain, period = "30d") {
    return this._request("GET", `/websites/${domain}/intelligence/`,
      { params: { period, include_trends: "true" } });
  }
  getContentGaps(domain, page = 1) {
    return this._request("GET", `/websites/${domain}/content-gaps/`, { params: { page } });
  }
  getAISO(domain) {
    return this._request("GET", `/websites/${domain}/aiso/`,
      { params: { period: "30d", include_history: "true" } });
  }
  async analyzePage(domain, url, timeoutMs = 120000) {
    const result = await this._request("POST", `/websites/${domain}/analyze/`, { body: { url } });
    const deadline = Date.now() + timeoutMs;
    while (Date.now() < deadline) {
      const status = await this._request("GET", result.status_url);
      if (status.status === "completed") return status;
      if (status.status === "failed") throw new Error("Analysis failed");
      await new Promise(r => setTimeout(r, 5000));
    }
    throw new Error("Analysis timed out");
  }
}

// Usage
const client = new SEOJuiceClient("YOUR_API_TOKEN");
const intel = await client.getIntelligence("example.com");
console.log(`SEO Score: ${intel.seo_score}, AISO: ${intel.aiso_score}`);

Webhook Receiver

Flask app that verifies HMAC signatures and routes events to handlers.

Python — Flask
import hmac
import hashlib
import os

from flask import Flask, request, abort, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = os.environ["SEOJUICE_WEBHOOK_SECRET"]

def verify(payload: bytes, signature: str) -> bool:
    expected = hmac.new(WEBHOOK_SECRET.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

@app.route("/webhooks/seojuice", methods=["POST"])
def handle_webhook():
    signature = request.headers.get("X-SEOJuice-Signature", "")
    if not verify(request.get_data(), signature):
        abort(401)

    event = request.headers.get("X-SEOJuice-Event")
    data = request.json["data"]

    if event == "report.completed":
        print(f"Report {data['report_id']} ready — PDF: {data['pdf_url']}")
    elif event == "content_gap.found":
        print(f"New gap: {data['page_name']} (potential: {data['seo_potential']})")
    elif event == "aiso.score_changed":
        print(f"AISO {data['old_score']} → {data['new_score']} ({data['change']:+d})")
    elif event == "gbp.review_received":
        print(f"New {data['rating']}★ review from {data['author']}")

    return jsonify({"ok": True})

Claude Code / MCP

Use the SEOJuice API with AI coding assistants via the Model Context Protocol. Define tools so your AI assistant can query SEOJuice data directly.

MCP tool definition
{
  "name": "seojuice_intelligence",
  "description": "Get SEO intelligence summary for a website including SEO score, AISO score, content gaps, and trends.",
  "input_schema": {
    "type": "object",
    "properties": {
      "domain": {
        "type": "string",
        "description": "The website domain, e.g. example.com"
      },
      "period": {
        "type": "string",
        "enum": ["7d", "30d", "90d"],
        "description": "Time period for metrics"
      }
    },
    "required": ["domain"]
  }
}

Example prompts for AI assistants:

  • "Use the SEOJuice API to get intelligence for mysite.com and tell me what to prioritize."
  • "Fetch content gaps for mysite.com and draft outlines for the top 5 by SEO potential."
  • "Check my AISO score trends and suggest how to improve AI search visibility."
  • "Analyze my competitor landscape and identify keywords where I'm close to overtaking them."