What You'll Build

You will build a system that automatically captures anything you send it from anywhere a browser extension, a chat message, an email forward and stores it as a richly tagged, summarized page in your Notion database. The brain behind this system is Claude's API, which processes incoming content, extracts key topics, generates a concise summary, and assigns tags. A lightweight webhook server acts as the entry point, and the Notion API handles storage. By the end, you will have a scalable, programmatic second brain that never forgets and organizes itself.

Prerequisites

  • Python 3.10+ installed on your machine
  • A free Notion account (Team or Enterprise plan for API access)
  • A Claude API key from Anthropic
  • Basic familiarity with Python and HTTP requests
  • A tool like ngrok or a public server to test webhooks locally

1. Why Your Second Brain Needs an API

The traditional second brain is a manual process. You read something interesting, you open Notion, you create a page, you type tags, you paste the URL. Then you forget to do it again. The friction of capture kills consistency. Most people's Notion databases become graveyards of half finished pages and untagged links.

An AI powered second brain solves this by removing every manual step. Instead of opening Notion, you send a message to a Slack bot, click a browser button, or forward an email. That signal hits a webhook, which hands the raw text to Claude. Claude extracts the essence, generates tags, and writes the page into your Notion database automatically. You never touch the keyboard.

The architecture is straightforward. The Notion API provides a structured way to create pages and update properties. Claude's API (specifically the function calling feature) turns the AI into a decision engine: it looks at the content and decides what to write into Notion. Webhooks glue it all together, allowing capture from any source. For developers, this means you own every part of the stack. No vendor lock in, no subscription bloat, no limit on how many captures you can make.

This approach beats off the shelf alternatives because you can tailor the tagging logic, the summary length, and the database schema to your exact workflow. If you want Claude to prioritize code snippets over general text, you write a single prompt change. That kind of control matters when your knowledge base grows into hundreds or thousands of pages. For a deeper look at building custom AI assistants, see our guide on AI meeting notes assistant.

Let's start building.

2. Setting Up Notion as Your Knowledge Base

First, you need a Notion integration. Go to the Notion Developers portal and create a new internal integration. Copy the Internal Integration Secret (your API key). Then share the database you intend to use with that integration by going to the database page, clicking the three dots menu, selecting "Add connections", and choosing your integration.

Database Schema

Your database needs a few core properties to support AI enrichment:

  • Title (default title property) will hold the page name
  • Content (rich text or long text) stores the raw captured text or URL
  • Tags (multi-select) for AI generated topics
  • Summary (rich text) for the AI generated summary
  • Source (select) to indicate where the capture came from (Slack, email, browser)
  • Captured At (date) for timestamp

Writing Python Code to Create a Page

Now write a simple function that sends a POST request to the Notion API. You'll use the requests library.

import requests
import json

NOTION_TOKEN = "ntn_your_secret_token_here"
DATABASE_ID = "your_database_uuid_here"
NOTION_API = "https://api.notion.com/v1/pages"

headers = {
    "Authorization": f"Bearer {NOTION_TOKEN}",
    "Content-Type": "application/json",
    "Notion-Version": "2022-06-28"
}

def create_notion_page(title, content, tags=None, summary=None):
    data = {
        "parent": {"database_id": DATABASE_ID},
        "properties": {
            "Title": {
                "title": [{"text": {"content": title}}]
            },
            "Content": {
                "rich_text": [{"text": {"content": content}}]
            },
            "Captured At": {
                "date": {"start": "2026-06-05"}  # replace with dynamic timestamp
            }
        }
    }
    if tags:
        data["properties"]["Tags"] = {
            "multi_select": [{"name": t} for t in tags]
        }
    if summary:
        data["properties"]["Summary"] = {
            "rich_text": [{"text": {"content": summary}}]
        }
    response = requests.post(NOTION_API, headers=headers, json=data)
    return response.json()

# Example usage
result = create_notion_page(
    title="Understanding Python Async IO",
    content="Asyncio is a library to write concurrent code using the async/await syntax.",
    tags=["Python", "Concurrency", "Async"]
)
print(result["id"])  # success: page ID

When you run the script, Notion creates a new page with the given title, content, and tags. Check your database to confirm the page appeared. This is the storage layer of your second brain.

Now that the Notion integration works, we need to give Claude the ability to call this function on your behalf.

3. Connecting Claude to Notion via Function Calling

Claude's API supports a feature called function calling (also called tool use). You define a function specification, and Claude decides when to invoke it based on the user's input. In our case, you will provide a function called create_notion_note that mirrors your Python function. When you send raw content to Claude, it will return a structured request to call that function with the appropriate parameters.

The magic is in the tools parameter of the messages endpoint. Here is how you define the function.

import anthropic

client = anthropic.Anthropic(api_key="your_claude_key")

tools = [
    {
        "name": "create_notion_note",
        "description": "Create a new note in Notion with a title, content, tags, and summary.",
        "input_schema": {
            "type": "object",
            "properties": {
                "title": {"type": "string"},
                "content": {"type": "string"},
                "tags": {"type": "array", "items": {"type": "string"}},
                "summary": {"type": "string"}
            },
            "required": ["title", "content"]
        }
    }
]

message = client.messages.create(
    model="claude-3-5-sonnet-20241022",  # note: version omitted in final text, use "claude"
    max_tokens=1024,
    tools=tools,
    messages=[
        {"role": "user", "content": "Save this article about Docker Compose best practices: ... (long text)"}
    ]
)

# The response may contain a tool_use block
for block in message.content:
    if block.type == "tool_use":
        func_name = block.name
        func_args = block.input
        if func_name == "create_notion_note":
            result = create_notion_page(
                title=func_args["title"],
                content=func_args["content"],
                tags=func_args.get("tags", []),
                summary=func_args.get("summary")
            )
            print("Created page:", result["id"])

Notice that the function call is not executed automatically. Claude returns the intention; your code must implement the actual API call to Notion. This separation gives you full control. You can validate the arguments, add logging, or even bypass the call if the content is spam.

This pattern is central to the developer second brain. Claude decides the structure, your code handles the execution. If you want to learn more about building custom skills for Claude, check out building your first Claude skill.

4. Automating Capture with Webhooks

Manual API calls are not automation. To capture from anywhere, you need a public endpoint that other services can hit. A webhook server using Flask or FastAPI does the job perfectly. Here is a minimal Flask example that accepts a POST request, sends the content to Claude, and stores the result in Notion.

from flask import Flask, request, jsonify
import os

app = Flask(__name__)

@app.route("/capture", methods=["POST"])
def capture():
    data = request.get_json()
    if not data or "content" not in data:
        return jsonify({"error": "Missing content"}), 400

    raw_content = data["content"]
    title = data.get("title", "Untitled Capture")

    # Send to Claude for processing
    claude_response = call_claude_tool(raw_content, title)

    if "error" in claude_response:
        return jsonify({"error": claude_response["error"]}), 500

    return jsonify({"page_id": claude_response.get("page_id")}), 200

def call_claude_tool(content, title):
    # This function replicates the logic from section 3
    # ... (omitted for brevity: same as previous code)
    # Returns dict with page_id or error
    pass

if __name__ == "__main__":
    app.run(port=5000)

Expose this locally with ngrok http 5000 to get a public URL. Now you can use services like Zapier, or even a simple cURL command, to send captures.

Test it from your terminal:

curl -X POST https://your-ngrok-url.ngrok.io/capture \
  -H "Content-Type: application/json" \
  -d '{"title": "Quick thought", "content": "Remember to use async generators for streaming data"}'

Within seconds, that thought appears in your Notion database as a fully processed page. You can now set up a browser bookmarklet, a Slack slash command, or an email forwarding rule that hits this webhook. The automation is complete.

For a deeper look at automating customer facing workflows, see customer support AI agent.

5. Organizing Automatically: AI Tagging and Summarization

The raw capture is just the start. The real value comes from Claude enriching the content with tags and a summary. In the function call above, you already asked Claude to provide tags and summary. But you can make this step more robust by handling edge cases: content that is already a summary, content in code blocks, or content that needs to be combined with user provided context.

Here is an async Python function that processes a Notion page retroactively. Suppose you have a batch of existing untagged pages. You can fetch them via the Notion API, send the content to Claude, and update the page properties.

import asyncio
import aiohttp

async def process_existing_page(page_id):
    # Fetch page content from Notion (simplified)
    # Then call Claude to get tags and summary
    # Then update Notion page using PATCH /v1/pages/{page_id}
    pass

async def batch_process_all():
    # Get all pages from database
    # For each, call process_existing_page
    # Use asyncio.gather for concurrency
    pass

The key is that Claude's prompt can be tuned. For a developer second brain, you might want tags like "Python", "Performance", "Deployment". You can instruct Claude explicitly: "Extract up to 5 technical tags. If the content is about a specific framework, always include that framework name as a tag."

This AI knowledge organization turns your Notion into a living taxonomy. Tags are no longer manual chores; they emerge from the content itself. Over time, your database becomes richly interconnected, making retrieval far more powerful.

If you are interested in how AI can transform other business data processes, read AI business analytics for founders.

6. Smart Retrieval: Querying Your Second Brain

Capture and organization are worthless if you cannot find what you stored. Notion's built in search is decent, but it is keyword based and does not understand semantic similarity. To build true semantic search for developers, you have two paths.

Path A: Embeddings with a Vector Database

Use an embedding model (available through the Claude API or a separate service like OpenAI) to turn each page's content and summary into a vector. Store those vectors in Pinecone or Supabase. When you query, embed the question, search for similar vectors, and return the corresponding Notion page IDs. This is the most accurate approach for large knowledge bases.

# Pseudocode for embedding search
def search(query):
    query_embedding = get_embedding(query)
    results = pinecone.index.query(
        vector=query_embedding,
        top_k=5,
        include_metadata=True
    )
    # results contain page IDs; fetch from Notion API
    pages = [fetch_notion_page(match["id"]) for match in results["matches"]]
    return format_response(pages)

Path B: Notion Search + Claude Re Ranking

If you want to avoid maintaining a vector database, use Notion's own search API and then send the search results to Claude for reranking. This is simpler but slower. Here is a minimal implementation.

def search_notion_with_claude(query):
    # Use Notion search endpoint
    notion_search_url = "https://api.notion.com/v1/search"
    body = {"query": query, "sort": {"direction": "descending", "timestamp": "last_edited_time"}}
    response = requests.post(notion_search_url, headers=headers, json=body)
    pages = response.json()["results"]

    # Prepare context for Claude
    context = "\n\n".join([
        f"Page: {p['properties']['Title']['title'][0]['text']['content']}\nSummary: {p['properties'].get('Summary', {}).get('rich_text', [{}])[0].get('text', {}).get('content', 'No summary')}"
        for p in pages[:10]
    ])

    claude_response = client.messages.create(
        model="claude",
        max_tokens=512,
        messages=[
            {"role": "user", "content": f"Given this query: '{query}', rank these pages by relevance. Return the top 3 page IDs with a brief reason.\n\n{context}"}
        ]
    )
    return claude_response.content

Both approaches let you ask natural language questions like "How did I implement OAuth in my last project?" and get the right page. For more on how AI changes search, see AI search in 2026.

7. Scaling and Pitfalls

When your second brain grows to hundreds of captures per day, you will hit limits. The Notion API has a rate limit of roughly 3 requests per second per integration. Claude's API also has per minute token limits. You need to implement exponential backoff with random jitter. Use a library like tenacity to automatically retry on 429 responses.

Token limits are another challenge. Large articles may exceed Claude's context window. Chunk the content before sending it, send multiple requests, and combine the summaries. Store the chunks separately in Notion or link them from a parent page.

Cost considerations: each Claude API call costs money. For a power user capturing 50 items per day, the monthly cost might be $10 to $30 depending on token usage. That is far cheaper than a dedicated knowledge management SaaS and gives you complete control. But monitor your usage and set budget alerts.

Security: never hardcode API keys. Use environment variables or a secrets manager. Validate all incoming webhook payloads with a shared secret to prevent unauthorized captures. Sanitize user input to avoid injection attacks, especially if you render content in a front end later.

For more on scalability in AI workflows, see AI business analytics.

Common Pitfalls

  • Webhook timeouts: If Claude takes too long, the webhook caller may time out. Use an async task queue like Celery or a simple thread pool to return an immediate "processing" response.
  • Duplicate captures: Your script might save the same article twice if the webhook fires twice. Implement idempotency keys in your database to avoid duplicates.
  • Claude hallucinating tags: The AI may invent tags that are irrelevant. Set a constrained list of allowed tags in the function definition, or ask Claude to only use tags from a predefined set.
  • Notion database property limits: Notion limits the number of multi select options per database. Clean up unused tags periodically with a scheduled script.

Next Steps

Your developer second brain is now live. Start integrating it into your daily workflow. Add a browser bookmarklet that sends the current page URL to your webhook. Connect it to your Slack workspace with a custom slash command. Forward newsletters to a dedicated email address that triggers the webhook via Zapier.

Then iterate: refine the Claude prompt to generate better tags, add a daily digest that summarizes your new captures, or build a simple front end dashboard that displays your knowledge graph. The architecture is yours to extend.

If you want to explore other AI powered productivity workflows, check out automating weekly reports with Claude or automating your personal brand.

Cover photo by vackground.com on Unsplash.