notion-sdk
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNotion SDK Skill
Notion SDK 技能
Control Notion programmatically using the official Python SDK. See PyPI for current version.
notion-client通过官方的 Python SDK以编程方式控制Notion。查看PyPI获取最新版本。
notion-clientWhen to Use This Skill
何时使用该技能
Use this skill when:
- Creating pages or databases in Notion via API
- Querying Notion databases programmatically
- Adding blocks (text, code, headings) to Notion pages
- Automating Notion workflows with Python
- Integrating external data sources with Notion
在以下场景使用该技能:
- 通过API在Notion中创建页面或数据库时
- 以编程方式查询Notion数据库时
- 向Notion页面添加内容块(文本、代码、标题)时
- 使用Python自动化Notion工作流时
- 将外部数据源与Notion集成时
Preflight: Token Collection
准备工作:收集令牌
Before any Notion API operation, collect the integration token:
AskUserQuestion(questions=[{
"question": "Please provide your Notion Integration Token (starts with ntn_ or secret_)",
"header": "Notion Token",
"options": [
{"label": "I have a token ready", "description": "Token from notion.so/my-integrations"},
{"label": "Need to create one", "description": "Go to notion.so/my-integrations → New integration"}
],
"multiSelect": false
}])After user provides token:
- Validate format (must start with or
ntn_)secret_ - Test with from
validate_token()scripts/notion_wrapper.py - Remind user: Each page/database must be shared with the integration
在进行任何Notion API操作前,先收集集成令牌:
AskUserQuestion(questions=[{
"question": "Please provide your Notion Integration Token (starts with ntn_ or secret_)",
"header": "Notion Token",
"options": [
{"label": "I have a token ready", "description": "Token from notion.so/my-integrations"},
{"label": "Need to create one", "description": "Go to notion.so/my-integrations → New integration"}
],
"multiSelect": false
}])用户提供令牌后:
- 验证格式(必须以或
ntn_开头)secret_ - 使用中的
scripts/notion_wrapper.py进行测试validate_token() - 提醒用户:每个页面/数据库必须与集成应用共享
Quick Start
快速开始
1. Create a Page in Database
1. 在数据库中创建页面
python
from notion_client import Client
from scripts.create_page import (
create_database_page,
title_property,
status_property,
date_property,
)
client = Client(auth="ntn_...")
page = create_database_page(
client,
data_source_id="abc123...", # Database ID
properties={
"Name": title_property("My New Task"),
"Status": status_property("In Progress"),
"Due Date": date_property("2025-12-31"),
}
)
print(f"Created: {page['url']}")python
from notion_client import Client
from scripts.create_page import (
create_database_page,
title_property,
status_property,
date_property,
)
client = Client(auth="ntn_...")
page = create_database_page(
client,
data_source_id="abc123...", # Database ID
properties={
"Name": title_property("My New Task"),
"Status": status_property("In Progress"),
"Due Date": date_property("2025-12-31"),
}
)
print(f"Created: {page['url']}")2. Add Content Blocks
2. 添加内容块
python
from scripts.add_blocks import (
append_blocks,
heading,
paragraph,
bullet,
code_block,
callout,
)
blocks = [
heading("Overview", level=2),
paragraph("This page was created via the Notion API."),
callout("Remember to share the page with your integration!", emoji="⚠️"),
heading("Tasks", level=3),
bullet("First task"),
bullet("Second task"),
code_block("print('Hello, Notion!')", language="python"),
]
append_blocks(client, page["id"], blocks)python
from scripts.add_blocks import (
append_blocks,
heading,
paragraph,
bullet,
code_block,
callout,
)
blocks = [
heading("Overview", level=2),
paragraph("This page was created via the Notion API."),
callout("Remember to share the page with your integration!", emoji="⚠️"),
heading("Tasks", level=3),
bullet("First task"),
bullet("Second task"),
code_block("print('Hello, Notion!')", language="python"),
]
append_blocks(client, page["id"], blocks)3. Query Database
3. 查询数据库
python
from scripts.query_database import (
query_data_source,
checkbox_filter,
status_filter,
and_filter,
sort_by_property,
)python
from scripts.query_database import (
query_data_source,
checkbox_filter,
status_filter,
and_filter,
sort_by_property,
)Find incomplete high-priority items
Find incomplete high-priority items
results = query_data_source(
client,
data_source_id="abc123...",
filter_obj=and_filter(
checkbox_filter("Done", False),
status_filter("Priority", "High")
),
sorts=[sort_by_property("Due Date", "ascending")]
)
for page in results:
title = page["properties"]["Name"]["title"][0]["plain_text"]
print(f"- {title}")
undefinedresults = query_data_source(
client,
data_source_id="abc123...",
filter_obj=and_filter(
checkbox_filter("Done", False),
status_filter("Priority", "High")
),
sorts=[sort_by_property("Due Date", "ascending")]
)
for page in results:
title = page["properties"]["Name"]["title"][0]["plain_text"]
print(f"- {title}")
undefinedAvailable Scripts
可用脚本
| Script | Purpose |
|---|---|
| Client setup, token validation, retry wrapper |
| Create pages, property builders |
| Append blocks, block type builders |
| Query, filter, sort, search |
| 脚本名称 | 用途 |
|---|---|
| 客户端设置、令牌验证、重试包装器 |
| 创建页面、属性构建器 |
| 追加内容块、内容块类型构建器 |
| 查询、过滤、排序、搜索 |
References
参考资料
- Property Types - All 24 property types with examples
- Block Types - All block types with structures
- Rich Text - Formatting, links, mentions
- Pagination - Handling large result sets
- 属性类型 - 包含示例的全部24种属性类型
- 内容块类型 - 包含结构的全部内容块类型
- 富文本 - 格式设置、链接、提及
- 分页 - 处理大型结果集
Important Constraints
重要限制
Rate Limits
速率限制
- 3 requests/second average (burst tolerated briefly)
- Use for automatic rate limit handling
api_call_with_retry() - 429 responses include header
Retry-After
- 平均3次请求/秒(短暂的突发请求可容忍)
- 使用自动处理速率限制
api_call_with_retry() - 429响应包含头
Retry-After
Authentication Model
认证模型
- Page-level sharing required (not workspace-wide)
- User must explicitly add integration to each page/database:
- Page → ... menu → Connections → Add connection → Select integration
- 需要页面级共享(而非工作区级)
- 用户必须显式将集成应用添加到每个页面/数据库:
- 页面 → ...菜单 → 连接 → 添加连接 → 选择集成应用
API Version (v2.6.0+)
API版本(v2.6.0+)
- Uses instead of
data_source_idfor multi-source databasesdatabase_id - Legacy still works for simple databases
database_id - Scripts handle both patterns automatically
- 使用替代
data_source_id以支持多源数据库database_id - 旧版仍适用于简单数据库
database_id - 脚本会自动处理两种模式
Operations NOT Supported
不支持的操作
- Workspace settings modification
- User permissions management
- Template creation/management
- Billing/subscription access
- 修改工作区设置
- 管理用户权限
- 创建/管理模板
- 访问计费/订阅信息
API Behavior Patterns
API行为模式
Insights discovered through integration testing (test citations for verification).
通过集成测试发现的洞察(可参考测试用例验证)。
Rate Limiting & Retry Logic
速率限制与重试逻辑
api_call_with_retry()| Error Type | Behavior | Wait Strategy |
|---|---|---|
| 429 Rate Limited | Retries | Respects Retry-After header (default 1s) |
| 500 Server Error | Retries | Exponential backoff: 1s, 2s, 4s |
| Auth/Validation | Fails immediately | No retry |
Citation: (lines 146-193)
test_client.py::TestRetryLogicapi_call_with_retry()| 错误类型 | 行为 | 等待策略 |
|---|---|---|
| 429 速率受限 | 重试 | 遵循Retry-After头(默认1秒) |
| 500 服务器错误 | 重试 | 指数退避:1秒、2秒、4秒 |
| 认证/验证错误 | 立即失败 | 不重试 |
Citation: (lines 146-193)
test_client.py::TestRetryLogicRead-After-Write Consistency
写后读一致性
Newly created blocks may not be immediately queryable. Add 0.5s minimum delay:
python
append_blocks(client, page_id, blocks)
time.sleep(0.5) # Eventual consistency delay
children = client.blocks.children.list(page_id)Citation: (line 298)
test_integration.py::TestBlockAppend::test_retrieve_appended_blocks新创建的内容块可能无法立即被查询到。添加至少0.5秒的延迟:
python
append_blocks(client, page_id, blocks)
time.sleep(0.5) # Eventual consistency delay
children = client.blocks.children.list(page_id)Citation: (line 298)
test_integration.py::TestBlockAppend::test_retrieve_appended_blocksv2.6.0 API Migration
v2.6.0 API迁移
| Old Pattern | New Pattern (v2.6.0+) |
|---|---|
| |
| |
Citation: (line 110)
test_integration.py::TestDatabaseQuery| 旧模式 | 新模式(v2.6.0+) |
|---|---|
| |
| |
Citation: (line 110)
test_integration.py::TestDatabaseQueryArchive-Only Deletion
仅支持归档删除
Pages cannot be permanently deleted via API - only archived (moved to trash):
python
client.pages.update(page_id, archived=True) # Trash, not deleteCitation: cleanup fixture (lines 72-76)
test_integration.py无法通过API永久删除页面——只能归档(移至回收站):
python
client.pages.update(page_id, archived=True) # Trash, not deleteCitation: cleanup fixture (lines 72-76)
test_integration.pyEdge Cases & Validation
边缘情况与验证
Property Builder Edge Cases
属性构建器边缘情况
| Input | Behavior | Valid? |
|---|---|---|
Empty string | Creates empty content | Yes |
Empty array | Clears multi-select/relations | Yes |
| Clears property value | Yes |
Zero | Valid number (not falsy) | Yes |
Negative | Valid number | Yes |
| Unicode/emoji | Fully preserved | Yes |
Citation: (lines 302-341)
test_property_builders.py::TestPropertyBuildersEdgeCases| 输入 | 行为 | 是否有效? |
|---|---|---|
空字符串 | 创建空内容 | 是 |
空数组 | 清除多选/关联属性 | 是 |
| 清除属性值 | 是 |
数字 | 有效数字(非假值) | 是 |
负数 | 有效数字 | 是 |
| Unicode/表情符号 | 完全保留 | 是 |
Citation: (lines 302-341)
test_property_builders.py::TestPropertyBuildersEdgeCasesInput Validation Responsibility
输入验证责任
Builders are intentionally permissive - validation happens at API level:
| Property | Builder Accepts | API Validates |
|---|---|---|
| Date | Any string | ISO 8601 only |
| URL | Any string | Valid URL format |
| Checkbox | Truthy values | Boolean expected |
Best Practice: Validate in your application before building properties.
Citation: (lines 347-376)
test_property_builders.py::TestPropertyBuildersInvalidInputs构建器设计为宽松模式——验证在API层面进行:
| 属性 | 构建器接受的输入 | API验证规则 |
|---|---|---|
| 日期 | 任意字符串 | 仅支持ISO 8601格式 |
| URL | 任意字符串 | 有效的URL格式 |
| 复选框 | 真值 | 期望布尔值 |
最佳实践:在构建属性之前,先在你的应用中进行验证。
Citation: (lines 347-376)
test_property_builders.py::TestPropertyBuildersInvalidInputsToken Validation
令牌验证
- Case-sensitive: Only lowercase and
ntn_validsecret_ - Format check happens before API call (saves unnecessary requests)
- Empty/whitespace tokens rejected immediately
Citation: (lines 196-224)
test_client.py::TestClientEdgeCases- 区分大小写:仅支持小写的和
ntn_前缀secret_ - 在API调用前进行格式检查(避免不必要的请求)
- 立即拒绝空值/仅含空白字符的令牌
Citation: (lines 196-224)
test_client.py::TestClientEdgeCasesQuery & Filter Patterns
查询与过滤模式
Compound Filter Composition
复合过滤器组合
python
undefinedpython
undefinedEmpty compound (matches all)
空复合过滤器(匹配所有内容)
and_filter() # {"and": []}
and_filter() # {"and": []}
Deep nesting supported
支持深度嵌套
and_filter(
or_filter(filter_a, filter_b),
and_filter(filter_c, filter_d)
)
_Citation: `test_filter_builders.py::TestFilterEdgeCases` (lines 323-360)_and_filter(
or_filter(filter_a, filter_b),
and_filter(filter_c, filter_d)
)
_Citation: `test_filter_builders.py::TestFilterEdgeCases` (lines 323-360)_Filter Limitations
过滤器限制
Filters don't exclude NULL properties - check in Python:
python
if row["properties"]["Rating"]["number"] is not None:
# Process non-null valuesCitation: (lines 120-135)
test_integration.py::TestDatabaseQuery::test_query_database_with_filter过滤器不会排除NULL属性——需在Python中检查:
python
if row["properties"]["Rating"]["number"] is not None:
# Process non-null valuesCitation: (lines 120-135)
test_integration.py::TestDatabaseQuery::test_query_database_with_filterPagination Invariants
分页不变量
| Condition | | |
|---|---|---|
| More results exist | | Present, non-None |
| No more results | | May be absent/None |
Always check before using .
has_morenext_cursorCitation: (lines 137-151)
test_integration.py::TestDatabaseQuery::test_query_database_with_pagination| 条件 | | |
|---|---|---|
| 存在更多结果 | | 存在且非空 |
| 无更多结果 | | 可能不存在/为空 |
始终在使用之前检查。
next_cursorhas_moreCitation: (lines 137-151)
test_integration.py::TestDatabaseQuery::test_query_database_with_paginationError Handling
错误处理
python
from notion_client import APIResponseError, APIErrorCode
try:
result = client.pages.create(...)
except APIResponseError as e:
if e.code == APIErrorCode.ObjectNotFound:
print("Page/database not found or not shared with integration")
elif e.code == APIErrorCode.Unauthorized:
print("Token invalid or expired")
elif e.code == APIErrorCode.RateLimited:
print(f"Rate limited. Retry after {e.additional_data.get('retry_after')}s")
else:
raisepython
from notion_client import APIResponseError, APIErrorCode
try:
result = client.pages.create(...)
except APIResponseError as e:
if e.code == APIErrorCode.ObjectNotFound:
print("页面/数据库未找到或未与集成应用共享")
elif e.code == APIErrorCode.Unauthorized:
print("令牌无效或已过期")
elif e.code == APIErrorCode.RateLimited:
print(f"速率受限。请在{e.additional_data.get('retry_after')}秒后重试")
else:
raiseInstallation
安装
bash
uv pip install notion-client # v2.6+ required for data_source supportOr use PEP 723 inline dependencies (scripts include them).
bash
uv pip install notion-client # 支持data_source需要v2.6+版本或者使用PEP 723内联依赖(脚本已包含)。
Troubleshooting
故障排除
| Issue | Cause | Solution |
|---|---|---|
| Object not found | Page not shared with integration | Share page: ... menu → Connections → Add integration |
| Unauthorized | Token invalid or expired | Generate new token at notion.so/my-integrations |
| Rate limited (429) | Too many requests | Use |
| Empty results from query | Filter matches nothing | Verify filter syntax and property names |
| Block not found after create | Eventual consistency delay | Add 0.5s delay after write before read |
| Invalid property type | Wrong builder used | Check property type in database schema |
| Token format rejected | Wrong prefix (case-sensitive) | Token must start with |
| Data source ID not working | Old API version | Upgrade notion-client to latest version |
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 对象未找到 | 页面未与集成应用共享 | 共享页面:...菜单 → 连接 → 添加集成应用 |
| 未授权 | 令牌无效或已过期 | 在notion.so/my-integrations生成新令牌 |
| 速率受限(429) | 请求过多 | 使用 |
| 查询返回空结果 | 过滤器未匹配任何内容 | 验证过滤器语法和属性名称 |
| 创建后找不到内容块 | 最终一致性延迟 | 写入后添加0.5秒延迟再进行读取 |
| 无效的属性类型 | 使用了错误的构建器 | 检查数据库架构中的属性类型 |
| 令牌格式被拒绝 | 前缀错误(区分大小写) | 令牌必须以 |
| 数据源ID无法使用 | API版本过旧 | 将notion-client升级到最新版本 |