Official Python SDK for the SEOJuice Intelligence API
Project description
SEOJuice Python SDK
Official Python SDK for the SEOJuice Intelligence API.
- Typed wrappers for all
/api/v2/endpoints - Sync and async clients (powered by httpx)
- Built-in pagination helpers
- Django and ASGI middleware for automatic SEO tag injection
- Full type annotations with PEP 561 support
Installation
pip install seojuice
With Django middleware support:
pip install seojuice[django]
Quick Start
from seojuice import SEOJuice
client = SEOJuice("your-api-key")
# List all websites
websites = client.list_websites()
for site in websites["results"]:
print(site["domain"])
# Get detailed website info
detail = client.get_website("example.com")
print(detail["seo_score"])
client.close()
Domain-Scoped Client
Use .website(domain) to scope every call to one domain without repeating it. The returned WebsiteResource is fully typed, so autocomplete and mypy work across its methods:
from seojuice import SEOJuice
with SEOJuice("your-api-key") as client:
site = client.website("example.com")
# Get website details
detail = site.detail()
# List pages
pages = site.pages(page_size=20)
for page in pages:
print(page["url"], page["seo_score"])
# Get intelligence summary with trends
intel = site.intelligence(period="30d", include_trends=True)
# Check AISO score
aiso = site.aiso(include_history=True)
print(f"AISO: {aiso['aiso_score']}")
# List content gaps
gaps = site.content_gaps(intent="informational")
for gap in gaps:
print(gap["page_name"], gap["seo_potential"])
Pagination
Manual Pagination
result = client.list_pages("example.com", page=1, page_size=10)
print(f"Total: {result.total_count}")
print(f"Has next: {result.has_next}")
for page in result:
print(page["url"])
Auto-Pagination
Iterate through all pages automatically:
from seojuice import SEOJuice, auto_paginate
client = SEOJuice("your-api-key")
for page in auto_paginate(
lambda **kw: client.list_pages("example.com", **kw),
page_size=100,
):
print(page["url"])
Async Usage
import asyncio
from seojuice import AsyncSEOJuice
async def main():
async with AsyncSEOJuice("your-api-key") as client:
site = client.website("example.com")
detail = await site.detail()
pages = await site.pages(page_size=50)
for page in pages:
print(page["url"])
asyncio.run(main())
Async Auto-Pagination
import asyncio
from seojuice import AsyncSEOJuice
from seojuice._pagination import async_auto_paginate
async def main():
async with AsyncSEOJuice("your-api-key") as client:
site = client.website("example.com")
async for page in async_auto_paginate(site.pages, page_size=100):
print(page["url"])
asyncio.run(main())
Error Handling
from seojuice import SEOJuice, AuthError, NotFoundError, RateLimitError, APIError
client = SEOJuice("your-api-key")
try:
detail = client.get_website("example.com")
except AuthError:
print("Invalid API key")
except NotFoundError:
print("Website not found")
except RateLimitError as e:
print(f"Rate limited: {e.message}")
except APIError as e:
print(f"API error [{e.status_code}]: {e.message}")
Exception hierarchy:
SEOJuiceError-- base exceptionAPIError-- any HTTP 4xx/5xxAuthError-- 401ForbiddenError-- 403NotFoundError-- 404RateLimitError-- 429ServerError-- 5xx
APIConnectionError-- connection refused, DNS failure, resetAPITimeoutError-- connect or read timeout
Everything the client can raise for a network round-trip is a SEOJuiceError, so a single except SEOJuiceError catches it all. A non-JSON error body (an HTML 502/504, an empty 503/429) surfaces as the typed ServerError/RateLimitError for its status code, not a raw JSONDecodeError. Connect/read timeouts and transport failures raise APITimeoutError/APIConnectionError.
The client does not retry. Wrap the call and retry on APITimeoutError, APIConnectionError, or RateLimitError if you want that.
Changes Management
Review, approve, reject, revert, and automate SEO changes over their lifecycle:
with SEOJuice("your-api-key") as client:
site = client.website("example.com")
# Get change statistics
stats = site.change_stats()
print(f"Total changes: {stats['total']}")
print(f"By status: {stats['by_status']}")
# List pending changes
pending = site.changes(status="pending", page_size=20)
for change in pending:
print(f"{change['change_type']}: {change['page_url']}")
print(f" {change['previous_value']} → {change['proposed_value']}")
# Approve a change
site.approve_change(change_id=42)
# Reject with reason
site.reject_change(change_id=43, reason="Not aligned with brand voice")
# Revert an applied change
site.revert_change(change_id=44, reason="Caused ranking drop")
# Bulk actions
result = site.bulk_change_action(
action="approve",
ids=[10, 11, 12],
)
print(f"Succeeded: {result['total_succeeded']}")
# Configure automation settings
site.update_change_settings(
auto_approve_internal_links=True,
auto_approve_meta_descriptions=False,
)
Action Items
List, create, and update SEO action items by priority and category:
with SEOJuice("your-api-key") as client:
site = client.website("example.com")
# Get action item summary
summary = site.action_item_summary()
print(f"Total: {summary['total']}, Open: {summary['open']}")
# List action items
items = site.action_items(priority="high", status="open")
for item in items:
print(f"[{item['priority']}] {item['title']}")
# Get grouped by category
groups = site.action_item_groups()
for group in groups:
print(f"{group['category']}: {group['count']} items")
# Create a new action item
site.create_action_item(
title="Fix missing alt tags on product images",
priority="high",
category="accessibility",
)
# Update status
site.update_action_item(item_id=99, action="complete")
Webhook Verification
Verify incoming webhook signatures using HMAC-SHA256:
from seojuice import verify_webhook_signature
# In your webhook handler
is_valid = verify_webhook_signature(
secret="your-webhook-secret",
body=request.body,
signature=request.headers.get("X-SEOJuice-Signature"),
)
if not is_valid:
return Response(status=401)
verify_webhook_signature fails closed: it returns False (never raises) when the secret, body, or signature is missing, None, or the wrong type. A request with no X-SEOJuice-Signature header rejects with 401 instead of raising a 500 inside your handler — so passing request.headers.get(...) straight through, as above, is safe. signature must be the raw hex digest from the header, with no sha256= prefix.
See examples/webhook_receiver.py for a complete Flask-based receiver.
Scores & Additional Endpoints
with SEOJuice("your-api-key") as client:
site = client.website("example.com")
# Domain health score
health = site.domain_health()
print(f"Health score: {health['overall_score']}")
# SERP landscape analysis
serp = site.serp_landscape()
# Industry benchmarks
benchmarks = site.benchmarks()
# Page-scoped endpoints (require page_id)
quality = site.content_quality(page_id=123)
geo = site.geo_readiness(page_id=123)
content = site.page_content(page_id=123)
# Submit URLs for processing
site.submit_urls(urls=[{"url": "https://example.com/new-page"}])
status = site.url_status(url="https://example.com/new-page")
PDF Report Download
with SEOJuice("your-api-key") as client:
site = client.website("example.com")
# Create a report
created = site.create_report("this_month")
print(f"Report queued: {created['report_id']}")
# Download PDF (once report is ready)
pdf_bytes = site.report_pdf(created["report_id"])
with open("report.pdf", "wb") as f:
f.write(pdf_bytes)
Django Middleware
Automatically inject SEO meta tags, Open Graph tags, and structured data into your HTML responses.
Setup
Add to your settings.py:
MIDDLEWARE = [
# ... other middleware ...
"seojuice.injection.django.SEOJuiceDjangoMiddleware",
]
# Optional settings
SEOJUICE_INJECTION_ENABLED = True # Default: True
SEOJUICE_INJECTION_TIMEOUT = 5.0 # Default: 5.0 seconds
The middleware fetches suggestions from smart.seojuice.io and injects:
<meta name="description">tag- Open Graph tags (
og:title,og:description,og:url,og:image) <title>tag injection when missing- JSON-LD structured data
Responses are cached in-memory with a 5-minute TTL. Non-HTML responses are passed through unchanged.
FastAPI / ASGI Middleware
For any ASGI framework (FastAPI, Starlette, etc.):
from fastapi import FastAPI
from seojuice.injection.asgi import SEOJuiceASGIMiddleware
app = FastAPI()
app.add_middleware(
SEOJuiceASGIMiddleware,
base_url="https://example.com",
timeout=3.0,
enabled=True,
)
Configuration
Both clients accept these constructor arguments:
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key |
str |
(required) | Your SEOJuice API key |
base_url |
str |
https://seojuice.com/api/v2 |
API base URL |
timeout |
float |
30.0 |
Request timeout in seconds |
http_client |
httpx.Client |
None |
Custom httpx client instance |
Constructing with an empty, whitespace, or None key raises ValueError("api_key is required") at call time — the client never sends Bearer None.
Flask Integration
Use before_request / after_request hooks for automatic SEO tag injection:
from flask import Flask, g, request
from seojuice.injection._fetcher import apply_suggestions, fetch_suggestions_sync
app = Flask(__name__)
@app.before_request
def fetch_seo():
g.seo_suggestions = fetch_suggestions_sync(request.url, timeout=3.0)
@app.after_request
def inject_seo(response):
suggestions = getattr(g, "seo_suggestions", None)
if suggestions and "text/html" in (response.content_type or ""):
html = response.get_data(as_text=True)
response.set_data(apply_suggestions(html, suggestions))
return response
Install with Flask extras:
pip install seojuice[flask]
Examples
| Example | Description |
|---|---|
intelligence_api.py |
Full Intelligence API workflow — overview, gaps, decay, PageSpeed |
changes_management.py |
Change lifecycle: stats, triage, bulk approve, review/reject, automation settings |
webhook_receiver.py |
Flask webhook receiver with HMAC-SHA256 signature verification |
action_items.py |
Action items: summary, listing, groups, create, update |
django_views.py |
Django views and class-based mixin for SEO context |
fastapi_app.py |
FastAPI app with async client lifecycle and ASGI middleware |
flask_app.py |
Flask app with request hooks and TTL cache |
async_workflow.py |
Concurrent fetching, async pagination, batch domain processing |
celery_tasks.py |
Background analysis, periodic decay checks, PDF report generation |
redis_cache.py |
Redis caching layer with pattern-based invalidation |
cms_integration.py |
Wagtail, Django CMS, and headless WordPress integrations |
Requirements
- Python 3.9+
- httpx >= 0.24.0
- typing_extensions >= 4.0
- django >= 3.2 (optional, for Django middleware)
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file seojuice-1.4.0.tar.gz.
File metadata
- Download URL: seojuice-1.4.0.tar.gz
- Upload date:
- Size: 67.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98c5da206eaa41117e6bdce6a8b0a42070be3671bf4a6df9822952678b56f6ef
|
|
| MD5 |
622e9cb0e89e11979b4f1ffd5c0279ee
|
|
| BLAKE2b-256 |
0c2b74c2ccf16629e072767ee6756fc152ccca4f713f10c6a58dd7908554b1ca
|
File details
Details for the file seojuice-1.4.0-py3-none-any.whl.
File metadata
- Download URL: seojuice-1.4.0-py3-none-any.whl
- Upload date:
- Size: 32.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
738c2d12c64189bd1f7e2146df8dfc39c73a515be01431bbcc55775f61cb2138
|
|
| MD5 |
4757f5ed39bb48936ce5eca18d625611
|
|
| BLAKE2b-256 |
0fd4b739a0f135e60a6dd318ffbbd1a007f8069f4e65d5e0911c51e04f26344e
|