Official Python client for Surmado's AI visibility testing and SEO reports
Project description
Surmado Python SDK
Official Python client for Surmado's AI visibility testing and SEO reports.
One-time reports. No subscriptions. API-first.
Installation
pip install surmado
Quick Start
from surmado import Surmado
client = Surmado() # uses SURMADO_API_KEY env var
# Run an AI Visibility Test
report = client.signal(
url="https://example.com",
brand_name="Example Brand",
email="you@example.com",
industry="E-commerce",
location="United States",
persona="Small business owners looking for affordable solutions",
pain_points="Finding reliable vendors, managing costs",
brand_details="Affordable solutions for growing businesses",
direct_competitors="Competitor A, Competitor B"
)
print(f"Report queued: {report['report_id']}")
# Wait for completion (or use webhooks)
completed = client.wait_for_report(report["report_id"])
print(f"PDF ready: {completed['download_url']}")
print("(Save for Surmado Solutions) Report Token: ", completed['token'])
Products
Signal — AI Visibility Testing
Test how your brand appears across 7 AI platforms: ChatGPT, Perplexity, Google Gemini, Claude, Meta AI, Grok, DeepSeek.
result = client.signal(
url="https://acme.com",
brand_name="Acme Corp", # max 100 chars
email="you@acme.com",
industry="B2B SaaS", # max 200 chars
location="United States", # max 200 chars
persona="CTOs at mid-market companies", # max 800 chars
pain_points="Integration challenges, lack of visibility", # max 1000 chars
brand_details="Modern, dev-focused tooling", # max 1200 chars
direct_competitors="Asana, Monday.com", # max 500 chars
)
Scan — SEO Auditing
Comprehensive technical SEO audits with prioritized recommendations.
result = client.scan(
url="https://acme.com",
brand_name="Acme Corp",
email="you@acme.com",
competitor_urls=["https://competitor1.com", "https://competitor2.com"]
)
Solutions — Strategic Advisory
Multi-AI strategic recommendations from 6 specialized agents.
Mode 1: With Signal Token (recommended)
# Run Signal first, then pass the token
signal_result = client.signal(...)
solutions_result = client.solutions(
email="you@acme.com",
signal_token=signal_result["token"]
)
Mode 2: Standalone
result = client.solutions(
email="you@acme.com",
brand_name="Acme Corp",
business_story="We're a B2B SaaS company in project management...",
decision="Should we expand to enterprise market?",
success="$10M ARR in 18 months",
timeline="Q2 2025",
scale_indicator="$2M ARR, 20 employees"
)
Mode 3: With Financial Analysis
result = client.solutions(
email="you@acme.com",
signal_token=signal_result["token"],
include_financial="yes",
financial_context="Growing but need to optimize costs",
monthly_revenue="$50K",
monthly_costs="$40K",
cash_available="$200K"
)
Rerun Methods
Once you've set up a brand with personas in the Surmado dashboard, run reports with minimal code:
# Signal: 3 fields instead of 10+
result = client.signal_rerun(
brand_slug="acme_corp",
persona_slug="cto-enterprise",
email="you@acme.com"
)
# Scan: 2 fields
result = client.scan_rerun(
brand_slug="acme_corp",
email="you@acme.com"
)
Perfect for Zapier/Make/n8n workflows, scheduled monitoring, and dashboard integrations.
Brand Management
# List all brands
brands = client.list_brands()
# Create a brand (fails if already exists)
brand = client.create_brand(
brand_name="Acme Corp",
url="https://acme.com",
industry="B2B SaaS"
)
# Create or get existing brand (never fails with conflict)
brand = client.ensure_brand(
brand_name="Acme Corp",
url="https://acme.com"
)
Async Reports & Polling
All reports process asynchronously (~15 minutes). Two ways to get results:
Polling
report = client.signal(...)
# Block until complete (default 20 min timeout)
completed = client.wait_for_report(report["report_id"], timeout_minutes=20)
print(completed["download_url"])
Webhooks
report = client.signal(
...,
webhook_url="https://your-server.com/webhook"
)
# Your webhook receives POST with full report data when complete
Report Data
Access raw report data (metrics, analysis) as JSON:
# Full data
data = client.get_report_data("rpt_abc123")
# Specific fields only
data = client.get_report_data("rpt_abc123", fields=["status", "insights"])
Handling Errors
When the API returns a non-success status code, a typed exception is raised:
from surmado import (
Surmado,
AuthenticationError,
InsufficientCreditsError,
NotFoundError,
ValidationError,
RateLimitError,
SurmadoError
)
client = Surmado()
try:
result = client.signal(...)
except AuthenticationError:
print("Invalid or missing API key")
except InsufficientCreditsError as e:
print(f"Not enough credits: {e.response}")
except RateLimitError:
print("Too many requests - back off and retry")
except NotFoundError:
print("Brand or report not found")
except ValidationError as e:
print(f"Invalid request params: {e}")
except SurmadoError as e:
print(f"API error: {e.status_code} - {e}")
Error Status Mapping
| Status Code | Exception |
|---|---|
| 400 | ValidationError |
| 401 | AuthenticationError |
| 402 | InsufficientCreditsError |
| 404 | NotFoundError |
| 422 | ValidationError |
| 429 | RateLimitError |
| >=500 | SurmadoError |
All exceptions inherit from SurmadoError and include status_code and response attributes.
Test Your Key
Verify your API key is valid without consuming credits:
result = client.test_auth()
print(f"Authenticated as: {result.get('org_id')}")
Timeouts
Default request timeout is 30 seconds. Configure per-client:
client = Surmado(timeout=60)
For wait_for_report, the polling timeout is separate:
# Wait up to 30 minutes for long reports
completed = client.wait_for_report(report_id, timeout_minutes=30)
Response Format
Report creation returns HTTP 202 Accepted:
{
"report_id": "rpt_abc123def456",
"token": "tok_xyz789abc123", # Save for Solutions Mode 1
"org_id": "org_xyz789",
"product": "signal",
"status": "queued",
"brand_slug": "example_brand",
"brand_name": "Example Brand",
"credits_used": 2,
"created_at": "2025-01-15T10:30:00Z"
}
Completed reports include download URLs (expire in 15 minutes):
{
"status": "completed",
"download_url": "https://storage.googleapis.com/...", # PDF
"pptx_download_url": "https://storage.googleapis.com/...", # PPTX
}
Webhook Payload
When using webhook_url, your endpoint receives a POST with this structure:
{
"event": "report.completed", # or "report.failed"
"timestamp": "2025-12-17T20:33:58.737743+00:00",
"report": {
"id": "daSwEVPimdjKStdgx3HS",
"token": "SIG-2025-12-3AHGD",
"product": "signal",
"status": "completed",
"data_url": "https://api.surmado.com/v1/reports/daSwEVPimdjKStdgx3HS",
"pdf_url": "https://api.surmado.com/v1/reports/view/C9VUr2VhSQvPG...",
"credits_refunded": False,
"summary": {
"business_name": "Acme Corp",
"contact_email": "you@example.com",
"industry": "B2B SaaS",
"brand_url": "https://acme.com",
"location": "San Francisco, CA",
"presence_score": 72,
"category_share": 18.9,
"authority_score": 85,
"competitive_rank": 1,
"competitive_tier": "Leader",
"top_competitor": "Competitor X",
"insights_summary": ["..."],
"pain_points_summary": ["..."]
}
}
}
Webhook Fields
| Field | Type | Description |
|---|---|---|
event |
string |
report.completed or report.failed |
timestamp |
string |
ISO 8601 when webhook was sent |
report.id |
string |
Report ID |
report.token |
string |
Report token (e.g., SIG-2025-12-3AHGD) |
report.product |
string |
signal, scan, or solutions |
report.status |
string |
completed or failed |
report.data_url |
string |
API endpoint to fetch full report |
report.pdf_url |
string |
Magic link to view PDF (30-day expiry) |
report.credits_refunded |
bool |
True if credits were refunded |
report.failure_reason |
string |
Error message (only present when status=failed) |
report.summary |
object |
Curated metrics (fields vary by product) |
Summary Fields (Signal)
| Field | Type | Description |
|---|---|---|
business_name |
string |
Brand name |
contact_email |
string |
Email from request |
industry |
string |
Industry category |
brand_url |
string |
Brand website |
location |
string |
Geographic location |
presence_score |
number |
AI visibility score (0-100) |
category_share |
number |
Share of category mentions (%) |
authority_score |
number |
Brand authority rating (0-100) |
competitive_rank |
number |
Rank among competitors |
competitive_tier |
string |
Leader, Challenger, etc. |
top_competitor |
string |
Highest-ranked competitor |
insights_summary |
string[] |
Key insights from analysis |
pain_points_summary |
string[] |
Customer pain points |
Summary Fields (Scan)
| Field | Type | Description |
|---|---|---|
business_name |
string |
Brand name |
contact_email |
string |
Email from request |
seo_score |
number |
Overall SEO score (0-100) |
performance_score |
number |
Page performance score (0-100) |
accessibility_score |
number |
Accessibility score (0-100) |
total_pages |
number |
Total pages analyzed |
schema_types_count |
number |
Number of schema markup types found |
critical_issues_count |
number |
Count of critical SEO issues |
critical_issues |
string[] |
List of critical issues found |
content_coverage_percentage |
number |
Content coverage (%) |
page_analysis_summary |
string |
Executive summary of page analysis |
link_analysis_summary |
string |
Executive summary of link analysis |
quick_wins |
object |
Combined quick wins from analysis |
Field Length Limits
| Field | Max Length |
|---|---|
| brand_name | 100 chars |
| industry | 200 chars |
| location | 200 chars |
| persona | 800 chars |
| pain_points | 1000 chars |
| brand_details | 1200 chars |
| direct_competitors | 500 chars |
| indirect_competitors | 500 chars |
| keywords | 500 chars |
| product | 1000 chars |
| business_story | 2000 chars |
| decision | 1500 chars |
| success | 1000 chars |
| timeline | 200 chars |
| scale_indicator | 100 chars |
Pricing
All reports are $50 each. No subscriptions.
| Product | Price | Credits |
|---|---|---|
| Signal | $50 | 2 |
| Scan | $50 | 2 |
| Solutions | $50 | 2 |
Versioning
This package follows SemVer. To check your installed version:
import surmado
print(surmado.__version__)
Links
- Docs: help.surmado.com/docs/api-reference
- API Key: surmado.com/login
- Examples: github.com/surmado/surmado-api-public
- Issues: github.com/surmado/surmado-python/issues
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 surmado-0.2.0.tar.gz.
File metadata
- Download URL: surmado-0.2.0.tar.gz
- Upload date:
- Size: 12.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
485355a64dd57af8b16ae274662d355605febc5434bb0f0c699aa44bce529be9
|
|
| MD5 |
92d01d3600375fd81c7deb61cc20b24e
|
|
| BLAKE2b-256 |
160138370a0daf585dd8cab362f15f7cd976ec2f77e4dc3f7cd67e28cb6847ac
|
File details
Details for the file surmado-0.2.0-py3-none-any.whl.
File metadata
- Download URL: surmado-0.2.0-py3-none-any.whl
- Upload date:
- Size: 13.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e5fe59f5875d0e1b78ebb7ff8a1a8bec01206005237be5d98f2f57e40158bdd
|
|
| MD5 |
acbdcd80590e95eee0bc28a19a45ce7d
|
|
| BLAKE2b-256 |
9423dfb14820bdb31b51808a110dfa16e56eb6b612c7718ff0cddc66e8768195
|