API Documentation
Programmatic access to SEO intelligence, AI visibility scores, content analysis, and automated optimizations.
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 -H "Authorization: Bearer YOUR_API_TOKEN" \
https://seojuice.com/api/v2/websites/
import requests
resp = requests.get(
"https://seojuice.com/api/v2/websites/",
headers={"Authorization": "Bearer YOUR_API_TOKEN"},
)
data = resp.json()
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:
{
"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:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number (1-indexed) |
| page_size | integer | 10-20 | Results per page (max 100) |
Every paginated response includes a 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": "not_found",
"message": "Website not found"
}
| Error Code | HTTP Status | Description |
|---|---|---|
| invalid_or_missing_token | 401 | No Authorization header or wrong scheme |
| invalid_token | 401 | Token not found in database |
| forbidden | 403 | Plan does not include this feature |
| not_found | 404 | Resource not found |
| invalid_pagination | 400 | Non-integer or out-of-range pagination params |
| missing_parameter | 400 | Required query parameter absent |
| invalid_request | 400 | Invalid JSON body |
| rate_limit_exceeded | 429 | Hourly rate limit exceeded |
Websites & Pages
/api/v2/websites/
List all websites in your organization. Returns all websites in a single array (no pagination).
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"}
]
}
/api/v2/websites/{domain}/
Get a single website with its latest report and key performance indicators.
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
}
}
/api/v2/websites/{domain}/pages/
List all pages for a website. Each page includes its internal links. Paginated.
| Parameter | Type | Required | Description |
|---|---|---|---|
| page | integer | No | Page number (default: 1) |
| page_size | integer | No | Results 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
}
]
}
]
}
/api/v2/websites/{domain}/pages/{page_id}/
Get a single page with its internal links.
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
https://seojuice.com/api/v2/websites/example.com/pages/4521/
/api/v2/websites/{domain}/links/
List all SEOJuice-generated internal links for a website. Paginated.
Show response
{
"pagination": {"page": 1, "page_size": 50, "total_count": 1842, "total_pages": 37},
"results": [
{
"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
}
]
}
Intelligence
/api/v2/websites/{domain}/intelligence/
Composite SEO intelligence summary including SEO score, AISO score, page counts, cluster counts, link metrics, and content gap count.
| Parameter | Type | Required | Description |
|---|---|---|---|
| period | string | No | 7d, 30d, or 90d (default: 30d) |
| include_history | boolean | No | Include time-series snapshot data |
| include_trends | boolean | No | Include period-over-period deltas |
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
/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
/api/v2/websites/{domain}/pagespeed/
Core Web Vitals and Lighthouse scores for a specific page URL.
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | Full 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
/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
}]
}
/api/v2/websites/{domain}/clusters/{cluster_id}/
Cluster detail with top keywords, time-series performance data, and quality history.
Content Gaps
/api/v2/websites/{domain}/content-gaps/
Content opportunities identified by AI analysis of competitor rankings, search volumes, and AI citation gaps.
| Parameter | Type | Required | Description |
|---|---|---|---|
| category | string | No | Filter by content category |
| intent | string | No | informational, 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
/api/v2/websites/{domain}/competitors/
Competitor analysis with keyword overlap, traffic estimates, and ranking comparisons.
| Parameter | Type | Required | Description |
|---|---|---|---|
| include_trends | boolean | No | Include 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
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| period | string | No | 7d, 30d, or 90d (default: 30d) |
| include_history | boolean | No | Include 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
/api/v2/websites/{domain}/similar/
Find semantically similar pages using vector embeddings. Powered by Pinecone. Results cached for 1 hour.
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | Full URL of the source page |
| limit | integer | No | Number 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
/api/v2/websites/{domain}/analyze/
Trigger an asynchronous on-demand analysis for a page. Returns immediately with an analysis ID to poll.
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
}
/api/v2/websites/{domain}/analyze/{analysis_id}/
Poll the status of an async page analysis. Status: queued → in_progress → completed | 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.
/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
}]
}
/api/v2/websites/{domain}/reports/
Generate a new SEO report. Returns immediately — poll the status URL for completion.
| Body field | Type | Required | Description |
|---|---|---|---|
| type | string | Yes | this_month, last_month, this_week, or last_week |
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"
}
/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/"
}
/api/v2/websites/{domain}/reports/{report_id}/pdf/
Download the report as a PDF file. Returns binary application/pdf.
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
-o report.pdf \
https://seojuice.com/api/v2/websites/example.com/reports/7/pdf/
Google Business Profile
/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"
}]
}
/api/v2/websites/{domain}/gbp/reviews/
Paginated list of Google Business Profile reviews with filtering options. Includes AI-generated reply suggestions.
| Parameter | Type | Required | Description |
|---|---|---|---|
| rating | integer | No | Filter by star rating (1-5) |
| sentiment | string | No | positive, negative, or neutral |
| needs_attention | boolean | No | Only unanswered or flagged reviews |
| location_id | integer | No | Filter 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}
}
/api/v2/websites/{domain}/gbp/reviews/{review_id}/reply/
Post a reply to a Google Business Profile review. Published directly to Google.
| Body field | Type | Required | Description |
|---|---|---|---|
| reply_text | string | Yes | The reply text to publish |
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.
{
"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
| Header | Description |
|---|---|
| X-SEOJuice-Event | Event type, e.g. report.completed |
| X-SEOJuice-Signature | HMAC-SHA256 hex digest of the raw request body |
| X-SEOJuice-Delivery-ID | Unique UUID for this delivery attempt |
| Content-Type | application/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:
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)
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
| Event | Trigger | Key Payload Fields |
|---|---|---|
| report.completed | Report generation finishes | report_id, type, json_url, pdf_url |
| report.failed | Report generation fails | report_id, error_message |
| cluster.updated | Cluster pages change | cluster_id, name, page_count_change |
| content_gap.found | New content gap detected | gap_id, page_name, seo_potential |
| aiso.score_changed | AISO score shifts ≥10 points | old_score, new_score, change |
| gbp.review_received | New Google Business review | review_id, rating, author, comment, sentiment |
| gbp.auto_reply_posted | Automatic reply sent | review_id, rating, reply |
Example Payload
Full payload for report.completed:
{
"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.
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.
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.
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.
{
"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."