DEV Community

dodou
dodou

Posted on

serpbase + Notion API Integration: Sync SERP Data to Notion Dashboard

Background

Marketing teams view SERP data through Notion dashboards (collaboration, views, filters are strong). Sync data from serpbase directly to Notion, 5-minute setup.

1. Setup

Notion Setup

  1. Create a Notion database
  2. Fields:
    • Date (Date)
    • Keyword (Title)
    • Rank (Number)
    • URL (URL)
    • Title (Text)
    • Snippet (Text)
  3. Create Internal Integration: https://www.notion.so/my-integrations
  4. Copy Integration Token
  5. Share Notion database with Integration (Share → Add connection)

2. Full Code (50 lines)

import os
import requests
from notion_client import Client
from datetime import datetime

NOTION_TOKEN = os.environ["NOTION_TOKEN"]
DATABASE_ID = "xxx_your_database_id"

notion = Client(auth=NOTION_TOKEN)

def call_serpbase(query, gl="us", num=10):
    r = requests.post(
        "https://api.serpbase.dev/google/search",
        headers={"X-API-Key": os.environ["SERPBASE_KEY"]},
        json={"q": query, "gl": gl, "num": num},
        timeout=10,
    )
    r.raise_for_status()
    return r.json()

def save_to_notion(query, data):
    today = datetime.now().strftime("%Y-%m-%d")

    for i, item in enumerate(data.get("organic", [])[:5], 1):
        existing = notion.databases.query(
            database_id=DATABASE_ID,
            filter={
                "and": [
                    {"property": "Date", "date": {"equals": today}},
                    {"property": "Keyword", "title": {"equals": query}},
                    {"property": "Rank", "number": {"equals": i}},
                ]
            },
        ).get("results", [])

        properties = {
            "Date": {"date": {"start": today}},
            "Keyword": {"title": [{"text": {"content": query}}]},
            "Rank": {"number": i},
            "URL": {"url": item.get("link", "")},
            "Title": {"rich_text": [{"text": {"content": item.get("title", "")[:100]}}]},
            "Snippet": {"rich_text": [{"text": {"content": item.get("snippet", "")}}]},
        }

        if existing:
            notion.pages.update(page_id=existing[0]["id"], properties=properties)
        else:
            notion.pages.create(parent={"database_id": DATABASE_ID}, properties=properties)

def main():
    queries = [
        "SERP API",
        "cheap SERP API",
        "SERP API selection",
    ]

    for q in queries:
        try:
            data = call_serpbase(q)
            save_to_notion(q, data)
            print(f"OK {q}: synced to Notion")
        except Exception as e:
            print(f"FAIL {q}: {e}")

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

3. Advanced Features (2)

Feature 1: Daily Report Notion Page

def create_daily_report():
    today = datetime.now().strftime("%Y-%m-%d")

    blocks = [
        {
            "object": "block",
            "type": "heading_1",
            "heading_1": {
                "rich_text": [{"type": "text", "text": {"content": f"SERP Daily Report {today}"}}]
            }
        },
        {
            "object": "block",
            "type": "paragraph",
            "paragraph": {
                "rich_text": [{"type": "text", "text": {"content": "Today's 50 keyword rank changes"}}]
            }
        }
    ]

    for kw in queries:
        blocks.append({
            "object": "block",
            "type": "paragraph",
            "paragraph": {
                "rich_text": [{"type": "text", "text": {"content": f"**{kw}**: data in table below"}}]
            }
        })

    blocks.append({
        "object": "block",
        "type": "embed",
        "embed": {
            "url": f"https://www.notion.so/{DATABASE_ID}?v=serp_{today}"
        }
    })

    notion.pages.create(
        parent={"page_id": "parent_page_id"},
        properties={"title": {"title": [{"text": {"content": f"SERP Daily Report {today}"}}]}},
        children=blocks,
    )
Enter fullscreen mode Exit fullscreen mode

Feature 2: Notion Views

In Notion database create Views:

  • View 1: Top 10 Opportunities (Rank ≤ 10)
  • View 2: Top 3 Rank (Rank ≤ 3)
  • View 3: Historical Trend (group by keyword, sort by date)
  • View 4: Competitor Compare (group by domain)

4. Scheduled Sync

import schedule

schedule.every().day.at("09:00").do(main)
Enter fullscreen mode Exit fullscreen mode

Or GitHub Actions:

on:
  schedule:
    - cron: '0 9 * * *'
jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
    - name: Sync
      env:
        SERPBASE_KEY: ${{ secrets.SERPBASE_KEY }}
        NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
        DATABASE_ID: ${{ secrets.NOTION_DB_ID }}
      run: python sync_to_notion.py
Enter fullscreen mode Exit fullscreen mode

5. 5 Engineering Details

Detail 1: Batch Creation

# Notion API rate limit 3 req/sec
import time
for item in items:
    notion.pages.create(...)
    time.sleep(0.4)  # ~2.5 req/sec
Enter fullscreen mode Exit fullscreen mode

Detail 2: Incremental Update

existing = notion.databases.query(
    database_id=DATABASE_ID,
    filter={"property": "Keyword", "title": {"equals": query}},
).get("results", [])

if existing:
    notion.pages.update(page_id=existing[0]["id"], properties=...)
else:
    notion.pages.create(...)
Enter fullscreen mode Exit fullscreen mode

Detail 3: Error Handling

from notion_client.errors import APIResponseError

def safe_create(properties):
    try:
        return notion.pages.create(...)
    except APIResponseError as e:
        if e.status == 429:
            time.sleep(60)  # rate limit
            return notion.pages.create(...)
        raise
Enter fullscreen mode Exit fullscreen mode

Detail 4: Batch Processing

# 100 queries then pause 1 minute
BATCH_SIZE = 100

for i in range(0, len(queries), BATCH_SIZE):
    batch = queries[i:i+BATCH_SIZE]
    for q in batch:
        sync_one(q)
    time.sleep(60)
Enter fullscreen mode Exit fullscreen mode

Detail 5: Notion Database Relations

properties = {
    "Date": {"date": {"start": today}},
    "Project": {"relation": [{"id": project_page_id}]},
    "Rank": {"number": i},
}
Enter fullscreen mode Exit fullscreen mode

6. Real Data (My 1-Month Project)

Metric Value
Syncs 30 (1/day)
Total records 1,500
Notion pages 31 (1 report + 30 data)
serpbase monthly cost $0.45
Engineer maintenance 0

7. vs Airtable

Dimension Notion Airtable
Free tier 5 users 1 base, 1000 rows
Database capability Strong (views, relations) Stronger (automation)
API docs Good Good
Integrations Many (API / Webhook) Many
Performance Slow (network) Fast
Best for Team collaboration Complex workflows

8. 4 Use Cases

Use Case 1: SEO Monitoring

# 50 keywords synced to Notion daily
for kw in 50_keywords:
    sync_one(kw)
Enter fullscreen mode Exit fullscreen mode

Use Case 2: Competitor Comparison

# Same keyword, 5 competitors
competitors = ["competitor1.com", "competitor2.com", ...]
for comp in competitors:
    sync_competitor_rank(comp, keyword)
Enter fullscreen mode Exit fullscreen mode

Use Case 3: Content Opportunities

# PAA to Notion
for kw in queries:
    data = call_serpbase(kw)
    for paa in data.get("people_also_ask", []):
        sync_paa_to_notion(kw, paa)
Enter fullscreen mode Exit fullscreen mode

Use Case 4: Client Reports

# Weekly client SERP reports
for client in clients:
    create_client_serp_report(client)
Enter fullscreen mode Exit fullscreen mode

Summary

serpbase + Notion 5 steps to SERP dashboard:

  1. Setup Notion database
  2. Pull SERP data
  3. Write Notion database
  4. Incremental update (avoid duplicates)
  5. Scheduled sync

50 lines of Python + Notion integration + 1 cron job, zero engineer maintenance, monthly cost $0.45.

Suits 5-50 person marketing teams that already use Notion for collaboration.

Top comments (0)