Skip to content
Back to skills

Sandbox Notion

ASecurity

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

  • 552 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 1, 2026
ai-agentsgonodeapidatabase

Works with

  • cursor
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 1, 2026

npx -y skills add nodetool-ai/nodetool --skill sandbox-notion --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Sandbox Notion?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Sandbox Notion
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/nodetool-ai-sandbox-notion/badge)](https://www.skillsdirectory.com/skills/nodetool-ai-sandbox-notion)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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.

Files in this skill

  • SKILL.md3.3 KB
  • package.json877 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.