Skip to main content

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:

  1. Create new page
  2. Type /database → Select “Table - Inline”
  3. 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:

  1. Trigger: Gmail - New email from hi@surmado.com with “Signal Report”
  2. Extract Token: Code by Zapier (regex pattern)
  3. Fetch JSON: Webhooks by Zapier GET request
  4. Calculate Metrics: Formatter (convert decimals to percentages)
  5. Determine Status: Formatter (Lookup Table)
    • Competitive Gap > 0: Leading
    • Gap -10 to 0: Competitive
    • Gap < -10: Behind
  6. 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

  1. Send test Signal report to yourself
  2. Verify Zap triggers
  3. Check Notion database for new page
  4. 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

  1. Go to Notion Integrations
  2. Click + New integration
  3. Name: Surmado Intelligence Token Integration
  4. Associated workspace: (select your workspace)
  5. Click Submit
  6. Copy Internal Integration Token (starts with secret_...)

Step 2: Share Database with Integration

  1. Open your Notion database page
  2. Click (top right) → Add connections
  3. Select Surmado Intelligence Token Integration
  4. Click Confirm

Step 3: Get Database ID

In Notion:

  1. Open database as full page
  2. Copy URL: https://notion.so/{workspace}/{database_id}?v=...
  3. 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:

  1. Create Businesses database (master list)
  2. Add “Signal Reports” and “Scan Reports” relation properties
  3. Link Signal/Scan pages to Business page
  4. 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:

  1. Export Notion database to CSV
  2. Upload to Google Sheets
  3. Create charts in Google Sheets
  4. Publish chart (File → Publish to web → Embed)
  5. 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:

  1. Verify database ID from URL
  2. 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
  • .env file for secrets (add to .gitignore)
  • README.md with 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.

Help 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
Contribute to This Article