Install
$ agentstack add skill-atlasomnia-donna-starter-notion Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Pipes remote content directly into a shell (remote code execution).
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Notion
Talk to Notion two ways. Same integration token works for both — pick by what's available.
◆ ntn CLI — Notion's official CLI. Shorter syntax, one-line file uploads, required for Workers. macOS + Linux only as of May 2026 (Windows support "coming soon"). Default when installed. ◆ HTTP + curl — works everywhere including Windows. Default fallback when ntn isn't installed.
Setup
1. Get an integration token (required for both paths)
- Create an integration at https://notion.so/my-integrations
- Copy the API key (starts with
ntn_orsecret_) - Store in
${HERMES_HOME:-~/.hermes}/.env:
`` NOTION_API_KEY=ntn_yo...re ``
- Ensure the integration is connected to the workspace — before sharing any pages, verify the integration appears in your workspace's connection list:
- Open any page in Notion → click Share (top-right) → ⚙️ Settings → Connections tab
- The integration should appear under Connected apps. If NOT listed, the API key is valid but the integration has no workspace linkage — every API call will return
404 object_not_foundregardless of page sharing. - To connect: go to https://notion.so/my-integrations → click your integration → in the Connected pages section, enable "Full workspace access" (or connect individual top-level pages manually).
- After connecting, verify it shows up in the Connections panel before proceeding.
- Share target pages/databases with the integration in Notion: page menu
...→Connect to→ your integration name. Without this, the API returns 404 for that page even though it exists.
1b. Grant workspace-wide access (optional, recommended for single-user workspaces)
To avoid connecting pages one by one, grant the integration full workspace access:
- Open https://notion.so/my-integrations
- Find your integration → click it
- Change access from Selected pages to Full workspace access (exact label may vary — look for a toggle or dropdown)
- This makes all existing and future pages visible to the integration by default
Tip: For single-user workspaces, Option 1b is the cleanest approach. For shared workspaces, connect individual top-level pages instead — child pages inherit access from their parent.
- For full workspace access (single-user workspaces): Open https://notion.so/my-integrations → click your integration → enable "Full workspace access" in the Connected pages section. This avoids having to manually connect every page. Child pages inherit access from their parent, so connecting top-level pages is sufficient if you prefer granular control.
2. Install ntn (preferred path on macOS / Linux)
### 2. Install `ntn` (preferred path on macOS / Linux)
```bash
# Recommended — use custom install dir if /usr/local/bin needs sudo
curl -fsSL https://ntn.dev | bash
# Or to a user-writable location (fallback when /usr/local/bin is locked):
curl -fsSL "https://ntn.dev" | NTN_INSTALL_DIR="$HOME/.local/bin" bash
# Or via npm (needs Node 22+, npm 10+)
npm install --global ntn
ntn --version # verify
macOS note: On recent macOS, /usr/local/bin may require sudo. Use NTN_INSTALL_DIR="$HOME/.local/bin" as a fallback — just ensure $HOME/.local/bin is on your PATH.
Skip ntn login — use the integration token instead. This works headlessly, no browser needed:
export NOTION_API_TOKEN=*** # ntn reads NOTION_API_TOKEN (not NOTION_API_KEY)
export NOTION_KEYRING=0 # don't try to use the OS keychain
Add those exports to your shell profile (or to ${HERMES_HOME:-~/.hermes}/.env) so every session inherits them.
> Pitfall: On macOS, curl -fsSL https://ntn.dev | bash fails with "Could not install to /usr/local/bin" — use NTN_INSTALL_DIR="$HOME/.local/bin" instead. Ensure $HOME/.local/bin is on your PATH.
3. Choose path at runtime
if command -v ntn >/dev/null 2>&1; then
# use ntn
else
# fall back to curl
fi
Windows users: skip step 2 entirely until native ntn ships — Path B works fine. If you want CLI ergonomics now, install ntn inside WSL2.
API Basics
Notion-Version: 2025-09-03 is required on all HTTP requests. ntn handles this for you. In this version, what users call "databases" are called data sources in the API.
Path A — ntn CLI (preferred, macOS / Linux)
Raw API calls (shorthand for curl)
ntn api v1/users # GET
ntn api v1/pages parent[page_id]=abc123 \ # POST with inline body
properties[title][0][text][content]="Notes"
ntn api v1/pages/abc123 -X PATCH archived:=true # PATCH; := is non-string (bool/num/null)
Syntax notes:
key=value— string fieldskey[nested]=value— nested object fieldskey:=value— typed assignment (booleans, numbers, null, arrays)
Search
ntn api v1/search query="page title"
Read page metadata
ntn api v1/pages/{page_id}
Read page as Markdown (agent-friendly)
ntn api v1/pages/{page_id}/markdown
Read page content as blocks
ntn api v1/blocks/{page_id}/children
Create page from Markdown
ntn api v1/pages \
parent[page_id]=xxx \
properties[title][0][text][content]="Notes from meeting" \
markdown="# Agenda
- Q3 roadmap
- Hiring"
Patch a page with Markdown
The current endpoint uses a command-style discriminated union. For a full replacement:
ntn api v1/pages/{page_id}/markdown -X PATCH \
type=replace_content \
replace_content[new_str]="## Update
Shipped the prototype."
For pages containing child pages or databases, prefer insert_content or update_content; replace_content refuses to delete protected child content unless allow_deleting_content=true.
Query a database (data source)
ntn api v1/data_sources/{data_source_id}/query -X POST \
filter[property]=Status filter[select][equals]=Active
For complex queries with sorts, multiple filter clauses, or compound logic, pipe JSON in:
echo '{"filter": {"property": "Status", "select": {"equals": "Active"}}, "sorts": [{"property": "Date", "direction": "descending"}]}' | \
ntn api v1/data_sources/{data_source_id}/query -X POST --json -
File uploads (one-liner — biggest CLI win)
ntn files create `Hello, ${name}!`,
});
Webhook capability
worker.webhook("onGithubPush", {
title: "GitHub Push Handler",
execute: async (events, { notion }) => {
for (const event of events) {
// event.body, event.rawBody (for signature verification), event.headers
console.log("got delivery", event.deliveryId);
}
},
});
After deploy: ntn workers webhooks list shows the URL Notion generates. Treat that URL as a secret — anyone with it can POST events unless you add signature verification.
Worker lifecycle commands
ntn workers deploy
ntn workers list
ntn workers exec -d '{"name": "world"}'
ntn workers sync trigger # run a sync now
ntn workers sync pause
ntn workers env set GITHUB_WEBHOOK_SECRET=...
ntn workers runs list # recent invocations
ntn workers runs logs
ntn workers webhooks list
When asked to build a Worker, scaffold with ntn workers new, write the code in src/index.ts, set any secrets with ntn workers env set, and deploy. Notion's docs at https://developers.notion.com/workers cover the full API surface.
Notion-Flavored Markdown (used by /markdown endpoints)
Standard CommonMark plus XML-like tags for Notion-specific blocks. Use tabs for indentation.
Blocks beyond CommonMark:
Ship the MVP by **Friday**.
Toggle title
Children indented one tab
Left side
Right side
Inline:
- Mentions: `
,Title,` - Underline:
text - Color:
textor block-level{color="blue"}on the first line - Math: inline
$x^2$, block$$ ... $$ - Citations:
[^https://example.com]
Colors: gray brown orange yellow green blue purple pink red, plus *_bg variants for backgrounds.
Headings 5/6 collapse to H4. Multiple > lines render as separate quote blocks — use ` inside a single >` for multi-line quotes.
Choosing the Right Path
| Task | mac / Linux | Windows | |---|---|---| | Read/write pages, search, query databases | ntn api ... | curl | | Read a page for an agent to summarize | ntn api v1/pages/{id}/markdown | curl /markdown endpoint | | Upload a file | ntn files create /) and proceed with API calls. The CUA path is useful for diagnosing why the integration isn't visible — it reveals whether the issue is page sharing vs. workspace connection.
- Bot integrations cannot create workspace-root pages OR databases: The API v2025-09-03 requires
parent.page_idorparent.database_idfor all page AND database creation. Bot integrations (internal, not public) have nomember_page. To create top-level content, you MUST first have a manually-created parent page shared with the integration — then nest everything under it. The workspace-parent shortcut ("parent": {"type": "workspace"}) works for neither pages nor databases on bot integrations — both returnvalidation_errorasking forparent.page_id. - ntn CLI headless auth requires TWO env vars:
NOTION_API_TOKEN(the integration key) ANDNOTION_WORKSPACE_ID(from/v1/users/me→bot.workspace_id). WithoutNOTION_WORKSPACE_ID, ntn errors with "No workspace selected." Set both:
``bash export NOTION_API_TOKEN=*** export NOTION_WORKSPACE_ID= ``
- Archived datasources are stuck: Once a database (datasource) is archived via
PATCH /v1/data_sources/{id}, it cannot be unarchived through any endpoint —/v1/pages/,/v1/databases/, and/v1/data_sources/all return 400 or 404. Recreate instead of trying to restore. - Bulk archiving hits type mismatches: When searching for items to archive, database rows appear in search results but their IDs may not resolve on
/v1/pages/(404) nor on/v1/data_sources/(wrong type). The reliable path: collect unique parent database IDs from search results and archive the parent databases — their rows disappear with them.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: AtlasOmnia
- Source: AtlasOmnia/donna-starter
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.