Loading...
Loading...
Control Notion via Python SDK. TRIGGERS - Notion API, create page, query database, add blocks.
npx skill4agent add terrylica/cc-skills notion-sdknotion-clientAskUserQuestion(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_validate_token()scripts/notion_wrapper.pyfrom 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']}")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)from scripts.query_database import (
query_data_source,
checkbox_filter,
status_filter,
and_filter,
sort_by_property,
)
# 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}")| Script | Purpose |
|---|---|
| Client setup, token validation, retry wrapper |
| Create pages, property builders |
| Append blocks, block type builders |
| Query, filter, sort, search |
api_call_with_retry()Retry-Afterdata_source_iddatabase_iddatabase_idapi_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 |
test_client.py::TestRetryLogicappend_blocks(client, page_id, blocks)
time.sleep(0.5) # Eventual consistency delay
children = client.blocks.children.list(page_id)test_integration.py::TestBlockAppend::test_retrieve_appended_blocks| Old Pattern | New Pattern (v2.6.0+) |
|---|---|
| |
| |
test_integration.py::TestDatabaseQueryclient.pages.update(page_id, archived=True) # Trash, not deletetest_integration.py| 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 |
test_property_builders.py::TestPropertyBuildersEdgeCases| Property | Builder Accepts | API Validates |
|---|---|---|
| Date | Any string | ISO 8601 only |
| URL | Any string | Valid URL format |
| Checkbox | Truthy values | Boolean expected |
test_property_builders.py::TestPropertyBuildersInvalidInputsntn_secret_test_client.py::TestClientEdgeCases# Empty compound (matches all)
and_filter() # {"and": []}
# Deep nesting supported
and_filter(
or_filter(filter_a, filter_b),
and_filter(filter_c, filter_d)
)test_filter_builders.py::TestFilterEdgeCasesif row["properties"]["Rating"]["number"] is not None:
# Process non-null valuestest_integration.py::TestDatabaseQuery::test_query_database_with_filter| Condition | | |
|---|---|---|
| More results exist | | Present, non-None |
| No more results | | May be absent/None |
has_morenext_cursortest_integration.py::TestDatabaseQuery::test_query_database_with_paginationfrom 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:
raiseuv pip install notion-client # v2.6+ required for data_source support| 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 |