Best for
- Creating pages or databases in Notion via API
- Querying Notion databases programmatically
- Adding blocks (text, code, headings) to Notion pages
terrylica/cc-skills/plugins/productivity-tools/skills/notion-sdk/SKILL.md
Control Notion via Python SDK. TRIGGERS - Notion API, create page, query database, add blocks.
Decision brief
Control Notion programmatically using the official notion-client Python SDK. See PyPI for current version.
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/terrylica/cc-skills --skill "plugins/productivity-tools/skills/notion-sdk"Inspect the Agent Skill "notion-sdk" from https://github.com/terrylica/cc-skills/blob/a5f847b22ee5afa35677e446973a903d098cd1d4/plugins/productivity-tools/skills/notion-sdk/SKILL.md at commit a5f847b22ee5afa35677e446973a903d098cd1d4. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
python from scripts.querydatabase import ( querydatasource, checkboxfilter, statusfilter, andfilter, sortbyproperty, )
Creating pages or databases in Notion via API
Before any Notion API operation, collect the integration token:
Review the “1. Create a Page in Database” section in the pinned source before continuing.
Review the “2. Add Content Blocks” section in the pinned source before continuing.
Permission review
No configured static risk pattern was detected
This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.
Evidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 99/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 62 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Control Notion programmatically using the official notion-client Python SDK. See PyPI for current version.
Self-Evolving Skill: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.
Use this skill when:
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:
ntn_ or secret_)validate_token() from 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 |
|---|---|
notion_wrapper.py | Client setup, token validation, retry wrapper |
create_page.py | Create pages, property builders |
add_blocks.py | Append blocks, block type builders |
query_database.py | Query, filter, sort, search |
api_call_with_retry() for automatic rate limit handlingRetry-After headerdata_source_id instead of database_id for multi-source databasesdatabase_id still works for simple databasesInsights discovered through integration testing (test citations for verification).
api_call_with_retry() handles transient failures automatically:
| 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: test_client.py::TestRetryLogic (lines 146-193)
Newly created blocks may not be immediately queryable. Add 0.5s minimum delay:
append_blocks(client, page_id, blocks)
time.sleep(0.5) # Eventual consistency delay
children = client.blocks.children.list(page_id)
Citation: test_integration.py::TestBlockAppend::test_retrieve_appended_blocks (line 298)
| Old Pattern | New Pattern (v2.6.0+) |
|---|---|
client.databases.query() | client.data_sources.query() |
filter: {"value": "database"} | filter: {"value": "data_source"} |
Citation: test_integration.py::TestDatabaseQuery (line 110)
Pages cannot be permanently deleted via API - only archived (moved to trash):
client.pages.update(page_id, archived=True) # Trash, not delete
Citation: test_integration.py cleanup fixture (lines 72-76)
| Input | Behavior | Valid? |
|---|---|---|
Empty string "" | Creates empty content | Yes |
Empty array [] | Clears multi-select/relations | Yes |
None for number | Clears property value | Yes |
Zero 0 | Valid number (not falsy) | Yes |
Negative -42 | Valid number | Yes |
| Unicode/emoji | Fully preserved | Yes |
Citation: test_property_builders.py::TestPropertyBuildersEdgeCases (lines 302-341)
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: test_property_builders.py::TestPropertyBuildersInvalidInputs (lines 347-376)
ntn_ and secret_ validCitation: test_client.py::TestClientEdgeCases (lines 196-224)
# Empty compound (matches all)
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)
Filters don't exclude NULL properties - check in Python:
if row["properties"]["Rating"]["number"] is not None:
# Process non-null values
Citation: test_integration.py::TestDatabaseQuery::test_query_database_with_filter (lines 120-135)
| Condition | has_more | next_cursor |
|---|---|---|
| More results exist | True | Present, non-None |
| No more results | False | May be absent/None |
Always check has_more before using next_cursor.
Citation: test_integration.py::TestDatabaseQuery::test_query_database_with_pagination (lines 137-151)
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:
raise
uv pip install notion-client # v2.6+ required for data_source support
Or use PEP 723 inline dependencies (scripts include them).
| 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 api_call_with_retry() for automatic handling |
| 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 ntn_ or secret_ (lowercase) |
| Data source ID not working | Old API version | Upgrade notion-client to latest version |
After this skill completes, check before closing:
Only update if the issue is real and reproducible — not speculative.
Frequently asked questions
Control Notion programmatically using the official notion-client Python SDK. See PyPI for current version.
The source record exposes this install command: npx skills add https://github.com/terrylica/cc-skills --skill "plugins/productivity-tools/skills/notion-sdk". Inspect the command and pinned source before running it.
Alternatives
K-Dense-AI/scientific-agent-skills
Distributed computing for larger-than-RAM pandas/NumPy workflows. Use when you need to scale existing pandas/NumPy code beyond memory or across clusters. Best for parallel file processing, distributed ML, integration with existing pandas code. For out-of-core analytics on single machine use vaex; for in-memory speed use polars.
K-Dense-AI/scientific-agent-skills
Medicinal chemistry filters for compound triage. Apply drug-likeness rules (Lipinski, Veber, CNS), structural alert catalogs (PAINS, NIBR, ChEMBL), complexity metrics, and the medchem query language for library filtering.
K-Dense-AI/scientific-agent-skills
Use NeuroKit2 to build or audit reproducible research workflows for physiological time-series preprocessing, event/interval analysis, multimodal alignment, variability, and complexity. Trigger when code imports neurokit2 or needs its current APIs, schemas, and method-aware validation—not for diagnosis or device validation.
Jeffallan/claude-skills
Use when building high-performance async Python APIs with FastAPI and Pydantic V2. Invoke to create REST endpoints, define Pydantic models, implement authentication flows, set up async SQLAlchemy database operations, add JWT authentication, build WebSocket endpoints, or generate OpenAPI documentation. Trigger terms: FastAPI, Pydantic, async Python, Python API, REST API Python, SQLAlchemy async, JWT authentication, OpenAPI, Swagger Python.