Call the Notion API from a Code node or CodeAct action, with NodeTool's request builder and rich-text/block readers running on the host
Scanned 9/1/2026
Install to Claude Code
npx -y skills add nodetool-ai/nodetool --skill sandbox-notion --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Sandbox Notion?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/nodetool-ai-sandbox-notion)More formats (shields.io, HTML) on the badges page.
---
name: sandbox-notion
description: Call the Notion API from a Code node or CodeAct action, with NodeTool's request builder and rich-text/block readers running on the host
---
# Notion in the sandbox
Specifier: `@nodetool-ai/sandbox-notion`. Import it at the top of the body.
Notion is an ordinary HTTPS API and `fetch` reaches it. What costs a script its
first three attempts is everything around the call — the `Notion-Version`
header, the rich-text arrays every piece of text is wrapped in, and the block
tree a page's content comes back as. Those three are this pack.
**Nothing here sends a request.** `request` builds one; the guest's own `fetch`
sends it, under the run's fetch cap and SSRF guard.
## request — an authenticated Notion request
```js
import { request } from "@nodetool-ai/sandbox-notion";
const token = await nodetool.secrets.get("NOTION_API_KEY");
const req = await request({
token,
path: "search",
method: "POST",
body: { query: inputs.query, page_size: 10 }
});
const res = await fetch(req.url, req);
if (!res.ok) throw new Error(`Notion search failed: ${res.status}`);
return { results: res.json.results };
```
Options: `token` (required), `path` (required — relative to `/v1` unless it
starts with `/`), `method` (default `GET`), `body` (JSON-encoded when present),
`query` (query-string bag), `version` (default `2022-06-28`).
Returns `{url, method, headers, body?}`, which `fetch(req.url, req)` takes
whole.
## plainText — a rich-text array as a string
```js
import { plainText } from "@nodetool-ai/sandbox-notion";
const title = await plainText(page.properties.Name.title);
```
Takes the array Notion puts under every `title`, `rich_text` and heading.
Anything else gives `""`.
## toMarkdown — a page's blocks as markdown
```js
import { request, toMarkdown } from "@nodetool-ai/sandbox-notion";
const req = await request({ token, path: `blocks/${inputs.page_id}/children`, query: { page_size: 100 } });
const blocks = (await fetch(req.url, req)).json.results;
return { markdown: await toMarkdown(blocks) };
```
Handles paragraphs, the three heading levels, bulleted and numbered items,
to-dos, quotes, code fences and dividers. A block markdown has no form for —
a database, an embed, a synced block — contributes nothing rather than a
placeholder, so the result is text a model or a file can take as-is.
## Common flows
**Create a page in a database**
```js
const req = await request({
token, path: "pages", method: "POST",
body: {
parent: { database_id: inputs.database_id },
properties: { Name: { title: [{ text: { content: inputs.title } }] } }
}
});
const page = (await fetch(req.url, req)).json;
```
**Query a database**
```js
const req = await request({
token, path: `databases/${inputs.database_id}/query`, method: "POST",
body: { filter: { property: "Status", select: { equals: "Open" } }, page_size: 50 }
});
```
## Gotchas
- **Every export is async.** A host call is a round trip.
- **Nested blocks are not fetched.** `toMarkdown` is pure over the blocks you
hand it. A block with `has_children` needs its own
`blocks/{id}/children` call.
- **Notion paginates at 100.** Follow `next_cursor` with
`query: { start_cursor }` and watch the run's fetch cap.
- **The integration needs access.** A page shared with nobody returns 404 even
with a valid token — share it with the integration in Notion first.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!