Notion API Basics
Foundation skill for all Notion API operations. Covers authentication, the base curl pattern, property types, API version differences, and common troubleshooting.
Prerequisites
- Create an integration at https://notion.so/my-integrations
- Copy the API key (starts with
ntn_orsecret_) - Store it in
~/.hermes/.env:echo 'NOTION_API_KEY=ntn_your_key_here' >> ~/.hermes/.env - Important: Share target pages/databases with your integration in Notion (click "..." → "Connect to" → your integration name)
- Find a page/database ID: open it in Notion, copy the URL — the ID is the UUID segment (32 hex chars, dashes optional)
Pitfall: Check the key isn't commented out
grep '^NOTION_API_KEY=' ~/.hermes/.env
If you see # NOTION_API_KEY=... (commented out), uncomment it:
sed -i '' 's/^# NOTION_API_KEY=/NOTION_API_KEY=/' ~/.hermes/.env
The .env file is write-protected from Hermes tools — use sed via terminal.
Pitfall: Terminal sandbox points HOME elsewhere
When running inside a profile (e.g. Senna), $HOME resolves to ~/.hermes/profiles/<profile>/home — NOT ~. This means source ~/.hermes/.env fails. Always use the absolute path:
source ~/.hermes/.env
This applies to cron jobs and terminal() calls inside the profile sandbox.
Authentication Options
| Method | What it is | Page sharing needed? | Workspace scope |
|---|---|---|---|
| Internal Integration (recommended) | A bot with its own permissions | ✅ Share each page/database with the bot explicitly | One workspace |
| PAT (Personal Access Token) | Acts as you — has your existing permissions | ❌ No sharing needed — sees what you see | One user in one workspace |
| OAuth 2.0 | For apps distributed to other people's workspaces | ❌ Users pick pages during install | Any workspace |
Recommendation: Start with an Internal Integration unless the user explicitly asks for a PAT.
Base curl Pattern
All requests follow this pattern:
curl -s -X GET "https://api.notion.com/v1/..." \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" \
-H "Content-Type: application/json"
Key points:
Notion-Versionheader is required — this skill uses2025-09-03(latest)- Add
-sto suppress curl progress bars (cleaner output) - Pipe through
jqfor readable JSON:| jq '.results[0].properties' - For quick debugging:
| head -c 500or| python3 -m json.tool | head -40
Property Types Reference
When creating or updating database entries (pages), properties follow these formats:
| Type | JSON Format |
|---|---|
| Title | {"title": [{"text": {"content": "..."}}]} |
| Rich text | {"rich_text": [{"text": {"content": "..."}}]} |
| Select | {"select": {"name": "Option"}} |
| Multi-select | {"multi_select": [{"name": "A"}, {"name": "B"}]} |
| Date | {"date": {"start": "2026-01-15", "end": null}} |
| Checkbox | {"checkbox": true} |
| Number | {"number": 42} |
| URL | {"url": "https://..."} |
{"email": "user@example.com"} |
|
| Relation | {"relation": [{"id": "page_id"}]} |
Key API Version Differences (2025-09-03 vs older)
- Databases → Data Sources: Use
/v1/data_sources/endpoints for queries and retrieval - Two IDs per database:
database_id— used when creating pages (parent: {"database_id": "..."})data_source_id— used when querying (POST /v1/data_sources/{id}/query)
- Search finds data sources: Results return
"object": "data_source"with theirdata_source_id - Creating databases:
POST /v1/databaseswithinitial_data_source.properties(NOT top-levelproperties)
Notes
- Page/database IDs are UUIDs (with or without dashes)
- Rate limit: ~3 requests/second average (bursts allowed)
- The API cannot set database view filters — that's UI-only
- Use
is_inline: truewhen creating data sources to embed them in pages - No
DELETE /v1/databases/{id}endpoint — to remove a database, use the Notion UI
Troubleshooting
object_not_found (404)
Almost always a permissions issue, not a missing page. The integration hasn't been granted access.
Fix: In Notion, open the page → "..." → "Connect to" → select your integration name.
validation_error — property doesn't exist
The property name in your JSON doesn't match the schema. Fetch actual property names:
source ~/.hermes/.env
DS_ID=$(curl -s "https://api.notion.com/v1/databases/{database_id}" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" | jq -r '.data_sources[0].id')
curl -s "https://api.notion.com/v1/data_sources/$DS_ID" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" | jq '.properties | keys'
Properties silently dropped during database creation
If you used POST /v1/databases with top-level properties (old API pattern), the properties are silently dropped — only a default "Name" title property exists. Fix by PATCHing the data source (see notion-databases skill).
Load-bearing URLs
- API docs: https://developers.notion.com
- Integrations: https://notion.so/my-integrations
- Reference API: https://developers.notion.com/reference/intro