Contextator
ENTR

Sources

Notion

Indexing a Notion workspace through the API — creating an integration, sharing pages, what gets imported, and the export-zip alternative.

Updated:

Index a Notion workspace directly through the API: every page shared with an internal integration is rendered to Markdown and kept in sync. Pages that change are re-rendered; pages that stop being shared are removed.

If you would rather not create an integration, you can also upload a Notion Export → Markdown & CSV zip — see the export route at the bottom.

1. Create an integration

  1. Go to notion.so/profile/integrations and create an internal integration.
  2. Copy its token (it starts with ntn_ or secret_).

2. Share the pages with it

In Notion, open each page or database you want indexed and share it with the integration (… → Connections → your integration). Sharing a parent page shares its children.

Nothing is visible to Contextator until you do this — a brand-new integration sees nothing.

3. Add the source

Add source → Notion

Field Notes
Name The mount prefix. Immutable
Integration token Stored encrypted with SECRET_KEY, never shown again
Root pages or databases Optional. Page or database ids — or simply their URLs, the id is picked out. One per line. Leave empty to take everything shared with the integration

Press Test connection: it reads the integration’s own user and answers with its name (Connected as Docs Importer), so a wrong token is obvious before any indexing starts.

SECRET_KEY must be set (32+ characters) before a source can store a token. See Configuration.

What gets imported

Each page becomes one Markdown file, nested in a folder named after its parent page, with the title, Notion id, URL and last_edited_time in frontmatter:

notion/Engineering/Runbooks/Rotating the signing key.md

Rendered block types: paragraphs, all three heading levels (including the blocks folded under a toggleable heading), bulleted, numbered and to-do lists with their nesting, quotes, callouts, code blocks, tables, dividers, equations, bookmarks, images and file links. Unknown block types are skipped rather than failing the page, so a new Notion feature never breaks an import.

Limits per source: 5000 pages and 25 levels of nesting. Requests are throttled to about three per second, which is Notion’s documented rate limit — a large workspace takes a while on the first sync and is fast afterwards.

Keeping it in sync

At the start of every index run, Contextator pulls the workspace again and re-renders only pages whose last_edited_time changed. A page that is no longer shared, or was deleted, has its file removed.

Notion’s own push webhook works the other way around from a git provider’s: the URL is still pasted into a Notion subscription, the same pattern as Push Webhooks elsewhere, but the secret runs in the opposite direction. Open a 15-minute verification window from the source’s row first; Notion’s first delivery to that URL carries the token, which is pasted back into Notion’s Webhooks tab to finish verification (ADR-0049). Once verified it delivers its own push webhook — but it does not close every gap: an unshared page is noticed only on a full run. Until it is verified, or for that gap, syncing happens when a run happens: press Sync on the source, Re-index on the project, or call POST /api/projects/:id/reindex on a schedule — see Admin API.

When something is wrong

If the token is rejected, or none of the configured root pages can be read, the sync fails and says so. It does not report an empty workspace — which would otherwise delete every page already imported. The other sources of the project still index, and the project reports:

2/3 sources synced; notion: No configured Notion root could be read — 1a2b3c4d… (object_not_found)

If some roots are readable and others are not, the readable ones import and the failures are reported.

Symptom Cause and fix
Test connection fails with unauthorized Wrong or revoked token. Create a new one and paste it
The source imports nothing Nothing is shared with the integration yet. Share the pages (… → Connections)
A specific page is missing It — or its parent — is not shared with the integration, or it is deeper than 25 levels
object_not_found for a root id The id is wrong, or that page is not shared with the integration
A database’s rows are missing Share the database itself, not only the pages inside it

The export-zip alternative

If you cannot create an integration, export from Notion (… → Export → Markdown & CSV) and upload the zip as an Upload files source with content type Notion export. The 32-character page id Notion appends to every file and folder name is stripped from paths and from the links pointing at them, and the nested Part-1.zip inside the export is unpacked automatically.

The trade-off: nothing syncs by itself — you re-export and re-upload when the content changes.

Arrow keys to move, Enter to open.