Notion Integration Guide
Notion Integration Guide
Quick answer: Connect Surmado Intelligence Tokens to Notion databases via Notion API. Create database pages automatically when Signal/Scan/Solutions reports delivered. Track Presence Rate, Authority Score, Scan scores over time. Use Zapier (no-code) or Notion API with Python/JavaScript (custom). Build client dashboards, competitive trackers, historical trend analysis.
Reading time: 14 minutes
In this guide:
- Use Zapier for no-code automation by creating Signal Reports database (properties: Title, Token, Date, Presence Rate number, Authority Score number, Status select, Tier select), connecting Zapier workflow (Gmail trigger → extract token → fetch JSON → create page), auto-populating pages with metrics + competitive analysis + platform variance within minutes of report delivery
- Build custom Python integration by creating Notion integration token at notion.so/my-integrations, sharing database with integration, using notion-client library with create_signal_page() function to populate properties (Presence Rate/Authority Score/Ghost Influence) and page content blocks (Metrics Summary, Competitive Analysis, Platform Variance) from Intelligence Token JSON
- Create collaborative Signal tracking database with properties for Quarter (Select: Q1/Q2/Q3/Q4), Status (Formula: if Presence Rate > 0.5 “Leading” else “Needs Work”), Competitive Gap (Rollup from linked Competitors database), and filtered views (Kanban by Status, Table sorted by Presence Rate descending, Calendar by Report Date)
- Set up Solutions decision log with database for Question (Title), Top Recommendation (Text), Risk Level (Select: Low/Medium/High), Model Consensus (Formula counting 6-model agreement), Implementation Status (Select: Not Started/In Progress/Completed), and team @mention notifications when decisions need action
- Build executive dashboard page with linked database views showing KPIs (average Presence Rate: 42%, improved this quarter: +8%, at-risk clients: 3/15), embedded comparison charts from Google Sheets, and timeline view of quarterly improvements with automated Python bulk imports for historical token data
Two methods: Zapier (easier, no coding) or Notion API (custom, more flexible)
Method 1: Zapier Integration (No Code)
Use Case
Automatically create Notion database page for every Signal report.
Prerequisites
- Notion account
- Zapier account (Starter plan minimum)
- Notion integration connected to Zapier
Setup Steps
Step 1: Create Notion Database
Database name: Signal Reports
In Notion:
- Create new page
- Type
/database→ Select “Table - Inline” - Name database:
Signal Reports
Properties (columns):
- Report (Title): Business name + date
- Token (Text): Intelligence Token
- Date (Date): Report date
- Presence Rate (Number): 0-100
- Authority Score (Number): 0-100
- Ghost Influence (Number): 0-100
- Top Competitor (Text): Competitor name
- Competitive Gap (Number): Your rate - top competitor rate
- Status (Select): Leading / Competitive / Behind
- Tier (Select): Essential / Pro
Step 2: Create Zap
Detailed workflow: See Zapier Integration Guide
Summary:
- Trigger: Gmail - New email from hi@surmado.com with “Signal Report”
- Extract Token: Code by Zapier (regex pattern)
- Fetch JSON: Webhooks by Zapier GET request
- Calculate Metrics: Formatter (convert decimals to percentages)
- Determine Status: Formatter (Lookup Table)
- Competitive Gap > 0: Leading
- Gap -10 to 0: Competitive
- Gap < -10: Behind
- Create Notion Page: Notion - Create Database Item
Notion step configuration:
- Database:
Signal Reports - Report (Title):
{{business__name}} - {{created_at}} - Token:
{{token}} - Date:
{{created_at}} - Presence Rate: (calculated percentage)
- Authority Score:
{{metrics__authority_score}} - Ghost Influence: (calculated percentage)
- Top Competitor:
{{competitors__0__name}} - Competitive Gap: (calculated)
- Status: (from Formatter step)
- Tier:
{{tier}}
Page content (Notion blocks):
## Metrics Summary
**Presence Rate**: {{presence_rate}}%
**Authority Score**: {{authority_score}}/100
**Ghost Influence**: {{ghost_influence}}%
## Competitive Analysis
Top 3 Competitors:
1. {{competitors__0__name}}: {{competitor_0_rate}}%
2. {{competitors__1__name}}: {{competitor_1_rate}}%
3. {{competitors__2__name}}: {{competitor_2_rate}}%
## Platform Variance
• ChatGPT: {{platform_variance__chatgpt__presence_rate}}%
• Claude: {{platform_variance__claude__presence_rate}}%
• Gemini: {{platform_variance__gemini__presence_rate}}%
## Intelligence Token
`{{token}}`
[Fetch full JSON](https://api.surmado.com/intelligence/{{token}})
Step 3: Test and Enable
- Send test Signal report to yourself
- Verify Zap triggers
- Check Notion database for new page
- Enable Zap
Result: Every Signal report creates Notion page automatically
Method 2: Notion API with Python
Use Case
Custom integration with full control over page structure, advanced calculations, bulk imports.
Prerequisites
- Notion account
- Python 3.7+ environment
- Notion integration token
Setup Steps
Step 1: Create Notion Integration
- Go to Notion Integrations
- Click + New integration
- Name:
Surmado Intelligence Token Integration - Associated workspace: (select your workspace)
- Click Submit
- Copy Internal Integration Token (starts with
secret_...)
Step 2: Share Database with Integration
- Open your Notion database page
- Click … (top right) → Add connections
- Select
Surmado Intelligence Token Integration - Click Confirm
Step 3: Get Database ID
In Notion:
- Open database as full page
- Copy URL:
https://notion.so/{workspace}/{database_id}?v=... - Extract
database_id(32-character hex string)
Example URL:
https://www.notion.so/myworkspace/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4?v=...
↑ This is database_id
Step 4: Install Python Libraries
pip install requests notion-client
Step 5: Create Python Script
File: notion_signal_import.py
import requests
from notion_client import Client
from datetime import datetime
# Configuration
NOTION_TOKEN = "secret_YOUR_INTEGRATION_TOKEN"
DATABASE_ID = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" # Your database ID
SURMADO_API_BASE = "https://api.surmado.com/intelligence/"
# Initialize Notion client
notion = Client(auth=NOTION_TOKEN)
def fetch_surmado_data(token):
"""Fetch Intelligence Token data from Surmado API"""
try:
response = requests.get(f"{SURMADO_API_BASE}{token}")
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error fetching token {token}: {e}")
return None
def create_signal_page(data):
"""Create Notion database page from Signal data"""
# Extract metrics
metrics = data.get("metrics", {})
business = data.get("business", {})
competitors = data.get("competitors", [])
platform_variance = data.get("platform_variance", {})
# Calculate competitive gap
your_rate = metrics.get("presence_rate", 0) * 100
top_competitor_rate = competitors[0].get("presence_rate", 0) * 100 if competitors else 0
competitive_gap = your_rate - top_competitor_rate
# Determine status
if competitive_gap > 0:
status = "Leading"
elif competitive_gap > -10:
status = "Competitive"
else:
status = "Behind"
# Format platform variance
platform_text = "\n".join([
f"• {platform.title()}: {metrics.get('presence_rate', 0) * 100:.1f}%"
for platform, metrics in platform_variance.items()
])
# Competitor list
competitor_text = "\n".join([
f"{i+1}. {comp.get('name', 'Unknown')}: {comp.get('presence_rate', 0) * 100:.1f}%"
for i, comp in enumerate(competitors[:3])
])
# Create Notion page
new_page = {
"parent": {"database_id": DATABASE_ID},
"properties": {
"Report": {
"title": [
{
"text": {
"content": f"{business.get('name', 'Unknown')} - {data.get('created_at', '')[:10]}"
}
}
]
},
"Token": {
"rich_text": [
{
"text": {
"content": data.get("token", "")
}
}
]
},
"Date": {
"date": {
"start": data.get("created_at", "")[:10]
}
},
"Presence Rate": {
"number": round(your_rate, 1)
},
"Authority Score": {
"number": metrics.get("authority_score", 0)
},
"Ghost Influence": {
"number": round(metrics.get("ghost_influence", 0) * 100, 1)
},
"Top Competitor": {
"rich_text": [
{
"text": {
"content": competitors[0].get("name", "N/A") if competitors else "N/A"
}
}
]
},
"Competitive Gap": {
"number": round(competitive_gap, 1)
},
"Status": {
"select": {
"name": status
}
},
"Tier": {
"select": {
"name": data.get("tier", "essential").title()
}
}
},
"children": [
{
"object": "block",
"type": "heading_2",
"heading_2": {
"rich_text": [{"type": "text", "text": {"content": "Metrics Summary"}}]
}
},
{
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [
{"type": "text", "text": {"content": f"Presence Rate: {your_rate:.1f}%\n"}},
{"type": "text", "text": {"content": f"Authority Score: {metrics.get('authority_score', 0)}/100\n"}},
{"type": "text", "text": {"content": f"Ghost Influence: {metrics.get('ghost_influence', 0) * 100:.1f}%"}}
]
}
},
{
"object": "block",
"type": "heading_2",
"heading_2": {
"rich_text": [{"type": "text", "text": {"content": "Competitive Analysis"}}]
}
},
{
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [{"type": "text", "text": {"content": f"Top 3 Competitors:\n{competitor_text}"}}]
}
},
{
"object": "block",
"type": "heading_2",
"heading_2": {
"rich_text": [{"type": "text", "text": {"content": "Platform Variance"}}]
}
},
{
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [{"type": "text", "text": {"content": platform_text}}]
}
},
{
"object": "block",
"type": "heading_2",
"heading_2": {
"rich_text": [{"type": "text", "text": {"content": "Intelligence Token"}}]
}
},
{
"object": "block",
"type": "code",
"code": {
"rich_text": [{"type": "text", "text": {"content": data.get("token", "")}}],
"language": "plain text"
}
}
]
}
try:
created_page = notion.pages.create(**new_page)
print(f"Created Notion page for {business.get('name', 'Unknown')}")
return created_page
except Exception as e:
print(f"Error creating Notion page: {e}")
return None
def main():
# Example usage
token = input("Enter Intelligence Token: ").strip()
print(f"Fetching data for {token}...")
data = fetch_surmado_data(token)
if not data:
print("Failed to fetch data. Check token and try again.")
return
if data.get("product") != "signal":
print("Error: Token must be a Signal token (SIG-YYYY-MM-XXXXX)")
return
print("Creating Notion page...")
create_signal_page(data)
if __name__ == "__main__":
main()
Step 6: Run Script
python notion_signal_import.py
Prompt:
Enter Intelligence Token: SIG-2025-11-A1B2C
Output:
Fetching data for SIG-2025-11-A1B2C...
Creating Notion page...
Created Notion page for Phoenix Cool Air
Check Notion database: New page appears with all Signal data
Bulk Import Script
Use case: Import multiple historical Signal reports at once
Modify main() function:
def main():
# List of historical tokens
tokens = [
"SIG-2025-02-A1B2C",
"SIG-2025-05-D3E4F",
"SIG-2025-08-G5H6J",
"SIG-2025-11-K7L8M"
]
for token in tokens:
print(f"\nProcessing {token}...")
data = fetch_surmado_data(token)
if data and data.get("product") == "signal":
create_signal_page(data)
else:
print(f" Skipping {token} (invalid or not Signal token)")
# Respect Notion API rate limits (3 requests/second)
import time
time.sleep(0.4)
print("\nBulk import complete")
Run:
python notion_signal_import.py
Result: All 4 historical tokens imported to Notion database
Method 3: Scan Reports to Notion
Scan Database Schema
Properties:
- Website (Title): Website URL
- Token (Text): Scan Intelligence Token
- Date (Date): Scan date
- Overall Score (Number): 0-100
- Technical SEO (Number): 0-100
- Performance (Number): 0-100
- Accessibility (Number): 0-100
- Security (Number): 0-100
- Issues (Number): Total issues count
- High Severity (Number): High-priority issues
- Status (Select): Good / Needs Work / Critical
Python Function for Scan
Add to same script:
def create_scan_page(data):
"""Create Notion database page from Scan data"""
website = data.get("website", {})
scores = data.get("category_scores", {})
issues = data.get("issues", [])
# Count issues by severity
high_count = len([i for i in issues if i.get("severity") == "high"])
medium_count = len([i for i in issues if i.get("severity") == "medium"])
low_count = len([i for i in issues if i.get("severity") == "low"])
# Determine status
overall = data.get("overall_score", 0)
if overall >= 76:
status = "Good"
elif overall >= 61:
status = "Needs Work"
else:
status = "Critical"
# Format top 5 issues
sorted_issues = sorted(issues, key=lambda x: {"high": 0, "medium": 1, "low": 2}.get(x.get("severity", "low"), 3))
top_5 = sorted_issues[:5]
issues_text = "\n".join([
f"[{i.get('severity', 'N/A').upper()}] {i.get('page', 'N/A')}: {i.get('issue', 'N/A')}"
for i in top_5
])
# Create page (similar structure to Signal)
new_page = {
"parent": {"database_id": DATABASE_ID}, # Use Scan database ID
"properties": {
"Website": {
"title": [{"text": {"content": website.get("url", "Unknown")}}]
},
"Token": {
"rich_text": [{"text": {"content": data.get("token", "")}}]
},
"Date": {
"date": {"start": data.get("created_at", "")[:10]}
},
"Overall Score": {
"number": overall
},
"Technical SEO": {
"number": scores.get("technical_seo", 0)
},
"Performance": {
"number": scores.get("performance", 0)
},
"Accessibility": {
"number": scores.get("accessibility", 0)
},
"Security": {
"number": scores.get("security", 0)
},
"Issues": {
"number": len(issues)
},
"High Severity": {
"number": high_count
},
"Status": {
"select": {"name": status}
}
},
"children": [
{
"object": "block",
"type": "heading_2",
"heading_2": {
"rich_text": [{"type": "text", "text": {"content": "Score Breakdown"}}]
}
},
{
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [
{"type": "text", "text": {"content": f"Technical SEO: {scores.get('technical_seo', 0)}/100\n"}},
{"type": "text", "text": {"content": f"Performance: {scores.get('performance', 0)}/100\n"}},
{"type": "text", "text": {"content": f"Accessibility: {scores.get('accessibility', 0)}/100\n"}},
{"type": "text", "text": {"content": f"Security: {scores.get('security', 0)}/100"}}
]
}
},
{
"object": "block",
"type": "heading_2",
"heading_2": {
"rich_text": [{"type": "text", "text": {"content": "Top Issues"}}]
}
},
{
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [{"type": "text", "text": {"content": issues_text}}]
}
}
]
}
try:
created_page = notion.pages.create(**new_page)
print(f"Created Scan page for {website.get('url', 'Unknown')}")
return created_page
except Exception as e:
print(f"Error creating Scan page: {e}")
return None
Advanced Patterns
Pattern 1: Linked Databases
Use case: Separate databases for Signal and Scan, linked via business name
Setup:
- Create
Businessesdatabase (master list) - Add “Signal Reports” and “Scan Reports” relation properties
- Link Signal/Scan pages to Business page
- View all reports for a business in one place
Pattern 2: Automated Weekly Digest
Use case: Email summary of all Notion reports added this week
Python script:
def get_recent_reports(days=7):
"""Query Notion database for reports in last N days"""
from datetime import datetime, timedelta
cutoff_date = (datetime.now() - timedelta(days=days)).isoformat()
query = notion.databases.query(
database_id=DATABASE_ID,
filter={
"property": "Date",
"date": {
"on_or_after": cutoff_date
}
},
sorts=[
{
"property": "Date",
"direction": "descending"
}
]
)
return query.get("results", [])
def send_weekly_digest():
"""Send email with links to this week's reports"""
recent = get_recent_reports(days=7)
if not recent:
print("No reports this week.")
return
email_body = "Weekly Surmado Reports:\n\n"
for page in recent:
title = page["properties"]["Report"]["title"][0]["text"]["content"]
url = page["url"]
email_body += f"• {title}: {url}\n"
# Send email (use smtplib or email service API)
print(email_body)
# ... email sending code ...
Schedule: Run weekly via cron or task scheduler
Pattern 3: Notion Dashboard with Embeds
Use case: Embed charts showing Presence Rate trends
Setup:
- Export Notion database to CSV
- Upload to Google Sheets
- Create charts in Google Sheets
- Publish chart (File → Publish to web → Embed)
- Embed chart in Notion page (
/embed→ paste Google Sheets chart URL)
Result: Live-updating chart in Notion dashboard
Troubleshooting
Issue 1: “object page does not exist”
Symptom: Python script fails with 404 error
Causes:
- Database ID incorrect (copied wrong ID)
- Integration not shared with database
Fix:
- Verify database ID from URL
- Open database → … → Add connections → Select integration
Issue 2: “body failed validation”
Symptom: Page creation fails with validation error
Causes:
- Property type mismatch (sending text to number field)
- Property name doesn’t match database schema
Fix:
- Check database property names (case-sensitive)
- Verify property types match (text → rich_text, number → number, etc.)
Issue 3: Rate Limit Errors
Symptom: “rate_limited” error after multiple requests
Causes:
- Notion API limit: 3 requests/second
Fix:
- Add
time.sleep(0.4)between requests (0.4s = 2.5 requests/second)
Best Practices
1. Use Database Templates
Create template page in Notion database:
- Pre-formatted sections
- Placeholder values
- Consistent styling
Clone template via API instead of creating from scratch
2. Version Control Your Scripts
Store Python scripts in Git (exclude tokens):
notion_integration.py.envfile for secrets (add to.gitignore)README.mdwith setup instructions
3. Error Logging
Add logging to scripts:
import logging
logging.basicConfig(
filename='notion_integration.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logging.info(f"Processing token {token}")
logging.error(f"Failed to create page: {e}")
Review logs to debug issues
4. Separate Dev and Production Databases
Dev database: Test scripts, experiments Production database: Client-facing, stable data
Why: Avoid polluting production with test data
Frequently Asked Questions
Can I update existing Notion pages instead of creating new ones?
Yes. Use notion.pages.update() with page ID. Check if token already exists in database before creating new page (query database, if found, update instead of create).
Does Notion integration work with team workspaces?
Yes. Create integration in workspace settings, share databases with integration. All team members with database access can view pages created by integration.
Can I use Notion formulas with Intelligence Token data?
Yes. Notion formulas can reference properties populated by API. Example: prop("Presence Rate") - prop("Top Competitor Rate") to calculate gap.
How do I handle Solutions tokens in Notion?
Similar to Signal/Scan. Create recommendations array in page content blocks, use callout blocks for risk assessment, embed model perspectives as headings + paragraphs.
Can I trigger Notion updates from Surmado reports automatically?
Yes. Use Zapier (Method 1) for automatic triggers. Python script requires manual execution unless you set up your own webhook receiver or email monitoring.
Need help with custom Notion integration? Contact hi@surmado.com with your workspace structure and requirements. We can provide tailored scripts or integration consulting.
Was this helpful?
Thanks for your feedback!
Have suggestions for improvement?
Tell us moreHelp Us Improve This Article
Know a better way to explain this? Have a real-world example or tip to share?
Contribute and earn credits:
- Submit: Get $25 credit (Signal, Scan, or Solutions)
- If accepted: Get an additional $25 credit ($50 total)
- Plus: Byline credit on this article