Why Add AI Voice to Your Notion Workflow?
If you’ve ever found yourself juggling meetings, brainstorming sessions, and endless to‑do lists, you know how valuable “hands‑free” productivity can be. Voice‑first interfaces let you capture ideas, generate summaries, or even read back your notes without breaking your flow.
Combining text‑to‑speech (TTS) and voice cloning with Notion’s flexible database API opens up a whole new class of productivity hacks:
- Instant audio summaries of meeting notes that you can listen to on the go.
- Voice‑driven content creation—speak, have it transcribed, then let an AI voice read it back for polishing.
- Personalized audio reminders that sound like you (or your favorite narrator) and appear right inside Notion pages.
In this post I’ll walk you through a practical setup using the ElevenLabs API (my go‑to service for natural‑sounding speech), a little Python, and Notion’s REST API. By the end you’ll have a script that takes any Notion page, generates a high‑quality audio file, and uploads it back as an attachment—ready for your next commute.
Getting Started with ElevenLabs
ElevenLabs offers a robust TTS endpoint that supports voice cloning, SSML, and streaming output. Sign up with the affiliate link below to unlock a generous free tier and instantly get an API key.
Once you have your key, keep it safe—treat it like any other secret token.
Prerequisites
| Tool | Version |
|---|---|
| Python | 3.9+ |
requests library |
latest (pip install requests) |
| Notion integration token | created in Notion → Settings & Members → Integrations |
| Notion database/page ID | copy from the URL (https://www.notion.so/yourworkspace/Page-Name-<ID>) |
Step 1: Generate Audio with ElevenLabs (Python)
Below is a minimal function that sends plain text to ElevenLabs and saves the resulting MP3.
import requests
ELEVEN_API_KEY = "YOUR_ELEVENLABS_API_KEY"
VOICE_ID = "21m00Tcm4TlvDq8ikWAM" # default "Rachel" voice; replace with your cloned voice ID
def text_to_speech(text: str, output_path: str) -> None:
url = f"https://api.elevenlabs.io/v1/text-to-speech/{VOICE_ID}"
headers = {
"xi-api-key": ELEVEN_API_KEY,
"Content-Type": "application/json"
}
payload = {
"text": text,
"voice_settings": {
"stability": 0.75,
"similarity_boost": 0.85
}
}
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
with open(output_path, "wb") as f:
f.write(response.content)
# Example usage
if __name__ == "__main__":
sample = "Hey team, here’s a quick recap of today’s sprint planning."
text_to_speech(sample, "sprint_summary.mp3")
Key points
-
VOICE_IDcan be any voice you’ve created in the ElevenLabs dashboard, including cloned voices that sound like you. - Adjust
stabilityandsimilarity_boostto fine‑tune the prosody. - The endpoint returns a binary MP3 payload, which we write straight to disk.
If you prefer a quick curl test, here’s the equivalent:
curl -X POST "https://api.elevenlabs.io/v1/text-to-speech/21m00Tcm4TlvDq8ikWAM" \
-H "xi-api-key: YOUR_ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Your text goes here.",
"voice_settings": { "stability": 0.75, "similarity_boost": 0.85 }
}' \
--output output.mp3
Step 2: Pull Content from Notion
Let’s fetch the plain‑text content of a Notion page. The Notion API returns a block‑tree; we’ll flatten it into a simple string.
import os
import json
import requests
NOTION_TOKEN = "YOUR_NOTION_INTEGRATION_TOKEN"
PAGE_ID = "YOUR_NOTION_PAGE_ID"
def get_page_text(page_id: str) -> str:
url = f"https://api.notion.com/v1/blocks/{page_id}/children"
headers = {
"Authorization": f"Bearer {NOTION_TOKEN}",
"Notion-Version": "2022-06-28"
}
response = requests.get(url, headers=headers)
response.raise_for_status()
data = response.json()
# Simple flattening of paragraph and heading blocks
texts = []
for block in data.get("results", []):
if block["type"] in ("paragraph", "heading_1", "heading_2", "heading_3"):
rich_text = block[block["type"]].get("rich_text", [])
plain = "".join(rt["plain_text"] for rt in rich_text)
texts.append(plain)
return "\n".join(texts)
# Example
if __name__ == "__main__":
page_text = get_page_text(PAGE_ID)
print(page_text[:200]) # preview
Tip: For larger pages you’ll need to paginate using the
next_cursorfield. The same pattern applies—just loop untilhas_moreisFalse.
Step 3: Attach the Audio Back to Notion
Now that we have sprint_summary.mp3, we’ll upload it as a file block.
def upload_file_to_notion(page_id: str, file_path: str) -> None:
# Step 1: Get a temporary upload URL from Notion
upload_url = "https://api.notion.com/v1/files"
headers = {
"Authorization": f"Bearer {NOTION_TOKEN}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
payload = {
"type": "external",
"external": {"url": f"https://www.notion.so/file/{os.path.basename(file_path)}"}
}
# Notion actually expects you to host the file elsewhere.
# A simple workaround: use a public S3 bucket or a service like https://file.io
# For brevity, let’s assume the file is already publicly reachable at `public_url`.
public_url = "YOUR_PUBLIC_AUDIO_URL"
# Step 2: Create a file block on the page
block_url = f"https://api.notion.com/v1/blocks/{page_id}/children"
block_payload = {
"children": [
{
"object": "block",
"type": "file",
"file": {
"type": "external",
"external": {"url": public_url}
}
}
]
}
resp = requests.patch(block_url, headers=headers, json=block_payload)
resp.raise_for_status()
print("Audio attached to Notion page!")
# Usage (after you’ve uploaded the MP3 somewhere public)
upload_file_to_notion(PAGE_ID, "sprint_summary.mp3")
Why the extra step? Notion doesn’t host arbitrary binary uploads; you need a publicly reachable URL. Services like Amazon S3, Google Cloud Storage, or even a quick
curl -Tto file.io work fine for prototypes.
Putting It All Together
Below is a compact script that ties the three steps into a single command:
def main():
# 1️⃣ Pull page text
text = get_page_text(PAGE_ID)
if not text:
print("No readable content found.")
return
# 2️⃣ Generate audio
audio_path = "page_audio.mp3"
text_to_speech(text, audio_path)
print(f"Audio saved to {audio_path}")
# 3️⃣ Upload audio somewhere public (example using file.io)
with open(audio_path, "rb") as f:
upload_resp = requests.post(
"https://file.io",
files={"file": f}
)
upload_resp.raise_for_status()
public_url = upload_resp.json()["link"]
print(f"Public URL: {public_url}")
# 4️⃣ Attach to Notion
# Re‑use the block‑creation snippet but replace `public_url`
block_url = f"https://api.notion.com/v1/blocks/{PAGE_ID}/children"
headers = {
"Authorization": f"Bearer {NOTION_TOKEN}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
block_payload = {
"children": [
{
"object": "block",
"type": "file",
"file": {
"type": "external",
"external": {"url": public_url}
}
}
]
}
resp = requests.patch(block_url, headers=headers, json=block_payload)
resp.raise_for_status()
print("✅ Audio attached to Notion!")
if __name__ == "__main__":
main()
Run python notion_voice.py and watch your Notion page gain a fresh audio summary—perfect for listening during a walk or while driving.
Extending the Idea
-
Voice Cloning: Upload a short sample of your own voice to ElevenLabs, get a
voice_id, and replace the default ID intext_to_speech. Suddenly your notes sound like you speaking! - Automation Platforms: Hook the script into Zapier, Make (Integromat), or a scheduled GitHub Action to generate audio nightly for a “Daily Digest” page.
- Interactive Bots: Pair the TTS output with a speech‑to‑text service (e.g., Whisper) to build a bi‑directional voice assistant that reads from and writes to Notion.
Wrapping Up
Integrating AI voice into Notion doesn’t require a massive infrastructure—just a few API calls, a bit of Python, and a reliable TTS provider. By leveraging ElevenLabs, you get crystal‑clear, customizable speech that can be cloned, fine‑tuned, and deployed instantly.
Ready to give your Notion workspace a voice? Grab your ElevenLabs API key, try the snippets above, and start turning static pages into dynamic audio assets.
👉 Try ElevenLabs today and bring your notes to life: https://try.elevenlabs.io/kr07zfuqn1bp
Top comments (0)