# Notion

> Notion pages and databases as context for answers: reading, questions in comments, the research report as a page, scope, webhook and polling.

For a team, Notion is the same as Confluence: **written context** that is in neither
the task nor the code. Requirements, decisions, policies, runbooks — and tables: the Notion
databases where the team keeps tasks, registers and agreements. The integration lets the model
read all of this when an answer needs it, takes questions right in page comments
and can leave the result **as a page**, not only as a comment.

_Illustration: A question whose answer is written down only in Notion: the model went to the page with a tool and answered from it._

## Tools

| Tool | What it does |
|---|---|
| `search_pages` | searches pages and databases by words — in titles through Notion’s search and **in the text of pages in scope** on its own: Notion searches only titles, while a question is usually about what the page says. Words are compared by their stem, so inflections do not get in the way; a page is read once until its next edit |
| `get_page` | reads a whole page — as markdown with headings, lists, tables and code — together with its discussion |
| `get_page_children` | lists the nested pages and databases of a section |
| `get_database` | the structure of a database: its data sources and properties with types |
| `query_database` | database records — each with its properties as text; optionally with a filter on one property, in Notion’s dialect for its type |
| `get_comments` | the page’s discussions: threads, who wrote and when |
| `add_comment` | a comment on a page or a reply in a thread |
| `create_page` | a child page from markdown — within the connection’s scope |

The first six only read and are available wherever the model is given connector tools —
in “Ask” and in deep research. The two writing ones are not given to the model at any
stage: the connector itself uses them when, at the platform’s command, it returns an answer as a
comment or a research report as a child page — and only within the scope.

**A page arrives as markdown, not as a block tree.** Since API version 2026-03-11 Notion
returns and accepts a page in one call in its own markdown; the integration adds
nothing to it and truncates nothing — a long page stays long. A database record
arrives flat: each property as text, according to its type (a status — by name,
a date — as a date, a checkbox — “yes”/“no”).

## Requests on a page

Everything described in [“Tracker integrations”](/en/docs/connector/trackers) works
here too: `@buff <question>`, `@buff research …`, `@buff task …` or `/ask`, `/research`,
`/task` in a page comment is a question, a research or a task draft. The answer
arrives **in the same discussion thread**; a question in a new thread starts a new conversation.

<Callout>
In Notion the integration **cannot be mentioned with @**: it is not a person and does not
appear in the suggestions. The bot is addressed with a word in the text — the alias from the settings (`buff`) or
the integration’s name. Both are written as is; case does not matter.
</Callout>

A page has no code branch, so the answer is computed at the level of the development project — or
[space](/en/docs/quickstart/space) — that
the connection is bound to.

### Research — as a page

The answer to `/research` in Notion does not go into a comment: under the page where the question was asked,
a **child page** with the report appears — the text itself, not a picture of text — and a PDF document is
attached to it. A comment with a link to the report and to the
research in Buff arrives in the discussion thread. The page is created only within the scope.

## Scope

The connection lists **roots** — links or identifiers of pages and databases.
The bot works in them and in everything nested: a page counts as “in scope” if one of its
parents is a root (page → page → database → page …). Writing is
limited the same way: you cannot reply, create a page or a comment outside the scope.

<Callout>
**An empty list of roots means “everything open to the integration in Notion”.** Here the boundary
has already been drawn by Notion itself: the integration sees only the pages it was connected to. This
differs from trackers, where an empty list means “nowhere” — the semantics are decided for
each source separately.
</Callout>

The integration’s permissions in Notion are the outer boundary, the list of roots is the inner one; it is sensible to have
both: connect the integration to a section, not to the whole workspace.

## Events: webhook and polling

Notion has **no comment feed** for a workspace, and a new comment does not
change the page. So events work differently than for Confluence and Figma:

- **The webhook is parsed.** The `comment.created` event names the comment, the integration
  reads it through the API and answers within seconds. The receiver belongs to the connector, and it
  issues the link — see [tracker events](/en/docs/connector/trackers#events-polling-and-webhooks).
- **Polling is a safety net.** The integration goes round the pages in scope, a few per
  pass, and reads their comments. By default **every 5 seconds**: the interval is a connection
  setting, and for Notion the integration suggests five seconds. An idle pass costs one
  request; with Notion’s limit of 180 requests per minute per integration (600 on the
  Business and Enterprise plans) this fits with room to spare, and the integration waits out `429` answers
  as `Retry-After` asks.

### The signing token comes from Notion

Notion signs its calls with **its own** verification token, not with the secret from the link:
when a subscription is created, it makes one unsigned call with a `verification_token`, and that
has to be pasted into the confirmation form in Notion. The connector does this for you:

1. Turn on webhook reception for the connection and copy the issued link.
2. In the Notion integration settings (**Webhooks**) enter this link. Notion sends the token.
3. Open the connection in the connector: the token is already there, with a “Copy” button. Paste it
   into Notion and confirm the subscription; subscribe to the `comment.created` event.

From then on the connector checks every call’s `X-Notion-Signature` header with this
token. If the subscription is recreated, Notion sends a **new** token — the connector does not
silently replace the old one with it: it shows the new one on the connection page and keeps checking
calls with the old one until you accept the new one in the “Signing secret” field.

_Illustration: The setup form: the API address is already filled in; what remains is to name the roots, the polling interval and the integration token._

## Setup

### In Notion

1. Create an **internal integration** in the workspace settings (the
   “Integrations” → “Develop” section). It needs these capabilities: read content, insert
   content, read and insert comments; reading user information is
   optional — it provides authors’ names where Notion did not fill them in.
2. Copy the integration secret — this is the token for the “api” field in the connector.
3. **Connect the integration to pages**: on the section’s root page (or database),
   the “…” menu → “Connections” → choose the integration. Everything nested opens up along with it.

### In the connector

| Field | Value |
|---|---|
| API address | prefilled: `https://api.notion.com` — no need to change it |
| `api` credentials | the internal integration’s secret |
| Pages and databases | links or identifiers of the roots, comma-separated; empty — everything open to the integration |
| Other ways to address the bot | a short name, for example `buff` |
| Polling interval | seconds; empty — as the integration suggests, 5 |

The connectivity check says not “ok” but the workspace name, the integration name, how many
roots are in scope and how many of them are actually available to it.

From the command line: `connector install notion`, then
`connector instance set --plugin notion --version 1.0.0 --set roots=<link>,<link> --poll 5`.

_Illustration: Connectivity check: the workspace, the integration and how many roots are available._

## What the integration does not do

- **It does not edit existing pages and database records** — it only reads them,
  writes comments and creates new pages within the scope.
- **It does not copy or index** Notion content to us: what a particular answer
  needed is read and goes into that answer’s context.
- **It does not index in advance** — the text search reads the pages in scope at the moment of the question
  (once until the page’s next edit); in a large scope the first search takes time.
- **It does not run tasks in Notion** as a tracker would: a database is read as knowledge, and the bot does not move
  statuses in it.
