Notion Knowledge Sync
The Notion Knowledge Sync mirrors your Amdahl knowledge base into a Notion database in your own Notion workspace — a one-way export so your team can read and link to your KB documents from inside Notion. It is the only outbound connector: every other connector pulls a provider's data into Amdahl (see Connecting data sources); this one pushes Amdahl's knowledge base out to Notion.
What gets mirrored is the current (promoted) version of each document — the same version your workspace and agents read. Proposed-but-not-yet-promoted versions are not exported until a human promotes them, so what lands in Notion is your canonical reference library, not work-in-progress drafts.
This guide covers what the sync does, how to connect and configure it, what gets synced and when, the REST API, the MCP read resources, and the limits to know about.
This connector is in the
connector catalog reference as
notion_knowledge_sync (Docs category, OAuth, workspace-owned, single-instance).
What it does
- One-way mirror. Amdahl knowledge-base documents are written into a Notion database that the sync creates in your Notion workspace. Editing a document in Amdahl re-pushes it; the sync never reads Notion data back into Amdahl.
- Current version only (by default). Each Amdahl document is a version family; the sync mirrors the current (promoted) version. When you promote a new version in Amdahl, the Notion page updates to match.
- One Notion page per document. Each KB version family maps to exactly one
Notion page (kept stable across re-syncs, so links and bookmarks survive). The
database row carries the document title, an
Amdahl Doc ID, the version number, and a last-synced timestamp. - Self-healing. If you delete the mirrored page in Notion, the next sync recreates it. An hourly reconcile sweep backfills anything that was missed and brings drifted pages back in line.
The sync runs entirely inside Amdahl (there is no Notion-side puller to install) and writes to Notion through Notion's API on your behalf using the access you grant during connect.
How to connect
Connecting is an OAuth flow. In the common case that is the only step — authorize, pick one page during Notion's consent, and the sync auto-creates its database under that page and backfills. A configure step is only needed when you grant access to more than one page (so you can pick which one) or want to change the defaults later.
1. Authorize Notion (OAuth)
Connect notion_knowledge_sync the same way as any OAuth connector — from the
console Connections page, or via the REST API:
POST /connections
{ "connector_type": "notion_knowledge_sync", "name": "Acme KB → Notion" }The response is { "mode": "oauth_redirect", "authorize_url": "...", "connection": { ... } }.
Send the user to authorize_url; Notion asks them to approve the integration and
select which pages it can access. The callback stores the access token.
Notion uses page-scoped consent: the pages you pick during authorize are the pages the sync can write to. Grant access to the page you want the synced database created under (or a parent of it) — Notion only lets an integration write where it has been given access.
2. Auto-provision (the one-click path)
Right after the callback, the sync looks at the pages you granted:
- You granted exactly one page. The sync auto-creates the "Amdahl Knowledge
Base" database under that page (with the
Name,Amdahl Doc ID,Version, andLast Syncedproperties it populates) and kicks off a full backfill. No configure step is needed — you're done. - You granted more than one page. The sync can't guess which one to use, so it
waits for you to pick. The connection's status reports
"provisioning_state": "needs_parent_selection"; show the picker (below) and then callconfigurewith the chosen page. - You granted no usable pages. The status reports
"provisioning_state": "no_pages"— re-authorize and grant a page.
Poll GET /notion-sync/:id/status after connecting; its provisioning_state tells
you which case you are in.
3. Pick a parent page (only when you granted more than one)
List the pages the integration can access and let the user choose:
GET /notion-sync/:id/pages{
"pages": [
{ "id": "<page-id>", "title": "Engineering Wiki", "url": "https://notion.so/..." },
{ "id": "<page-id>", "title": "Sales Playbook", "url": "https://notion.so/..." }
]
}Then call configure with the chosen page id (see below).
4. Configure (pick a parent page + provision the database)
configure is the manual path — used to pick a parent page in the multi-page case,
or to change the defaults / re-point the sync later. It provisions the database
under the designated page and kicks a backfill:
POST /notion-sync/:id/configure
{
"parent_page_id": "<notion-page-id-or-url>",
"database_title": "Amdahl Knowledge Base",
"version_policy": "current_only",
"on_kb_delete": "archive",
"drift_policy": "preserve",
"include": { "starred_only": false }
}parent_page_id accepts either a bare Notion page id or a full Notion page URL.
Only parent_page_id is required; everything else takes a sensible default
(see Configure below). The response is
{ "connection_id": "...", "config": { ... }, "backfill_enqueued": <n> }, where
backfill_enqueued is how many documents were queued for the initial mirror.
Re-running configure re-provisions a fresh database and re-baselines — use it
to point the sync at a different parent page.
Configure options
The configure call (and the console settings panel) accept these knobs. They are stored on the connection and applied to every sync.
| Option | Values | Default | What it controls |
|---|---|---|---|
parent_page_id | a Notion page id | (required) | The Notion page the synced database is created under. |
version_policy | current_only / all_promoted | current_only | Which versions to mirror. current_only mirrors just the promoted version (respects the human promotion gate). |
on_kb_delete | archive / leave | archive | What happens to the Notion page when the source document is archived or deleted in Amdahl. archive trashes the page; leave keeps it. |
drift_policy | preserve / overwrite | overwrite | What happens when a human has edited the Notion page since the last sync. preserve keeps the human edit and skips; overwrite re-writes from Amdahl. |
include | scope filters (below) | all current docs | Which documents are in scope. |
enabled | true / false | true | Master on/off for this connection's sync. |
Include filters (include) narrow which documents mirror. Leave empty to
mirror every current document:
document_types: only documents whose KBdocument_typeis in this list.starred_only: only starred documents.group_ids: an explicit allowlist of document version-family ids.
What gets synced, and when
The sync fires on two paths.
Instantly, off your KB writes. When you upload or append to a knowledge-base document, or promote a version to current, the change is queued for Notion right away (off the write path, so it never slows the KB write). Rapid-fire edits to the same document collapse into one sync that always reflects the latest version.
Hourly, via the reconcile sweep. An hourly background sweep compares your current KB documents against what is mirrored and queues anything that is out of sync — unmapped (never synced), version-drifted, errored, or needing archival. This is what backfills an entire workspace on first connect and what self-heals anything the instant path missed (a transient Notion error, an exhausted retry, a page you deleted in Notion).
Removals. When a document is archived or hard-deleted in Amdahl, its Notion
page is trashed on the next sync if on_kb_delete is archive (the default), or
left in place if leave. Removals propagate either through the KB write hooks or
the hourly reconcile sweep.
A document whose content has not changed since the last push is skipped without a Notion round-trip (a content hash is compared), so backfills and reconcile sweeps are cheap and safe to repeat.
The REST API
The Notion-sync REST surface lives under the platform base URL
(https://api.amdahl.com/api/platform/v1) and authenticates with a bearer token.
See Authentication. :id is the Notion-sync connection id
from POST /connections (or GET /connections).
Read the config
GET /notion-sync/:idReturns the sync configuration plus how many documents are currently mirrored:
{
"connection_id": "11111111-1111-1111-1111-111111111111",
"name": "Acme KB → Notion",
"status": "connected",
"config": {
"enabled": true,
"target_mode": "database",
"parent_page_id": "<page-id>",
"database_id": "<database-id>",
"data_source_id": "<data-source-id>",
"version_policy": "current_only",
"on_kb_delete": "archive",
"drift_policy": "overwrite",
"include": {},
"provisioned_at": "2026-06-27T12:00:00.000Z"
},
"mapped_count": 42
}A non-Notion-sync or cross-tenant id returns null — a not-found that never
reveals whether the id exists in another workspace.
Read the live status (for a polling badge)
GET /notion-sync/:id/statusA lean projection for polling a status badge or confirming a backfill is progressing:
{
"connection_id": "11111111-1111-1111-1111-111111111111",
"status": "connected",
"enabled": true,
"provisioned": true,
"provisioning_state": "provisioned",
"database_id": "<database-id>",
"mapped_count": 42,
"recent": { "syncedLastHour": 7, "failedLastHour": 0, "skippedLastHour": 3 }
}provisioning_state reports where the connect-only auto-provision flow left off:
provisioned (the database was auto-created), needs_parent_selection (you granted
more than one page — show the picker and call configure), no_pages (no page was
granted), or none (auto-provision has not run).
List the integration's accessible pages (the picker)
GET /notion-sync/:id/pagesLists the Notion pages the connected integration can access — the parent-page
picker for when a Notion authorize granted more than one page. Returns
{ "pages": [ { "id", "title", "url" }, ... ] }. A non-Notion-sync or cross-tenant
id returns an empty list.
Read the activity ledger
GET /notion-sync/:id/sendsOutbound connectors have no upstream sync-run history, so the per-document sync
ledger is the activity log. Optional ?limit= (1–200, default 50). The response
is the recent outcomes (newest-first) plus an hourly summary:
{
"rows": [
{
"id": "...",
"documentGroupId": "<doc-group-id>",
"notionPageId": "<notion-page-id>",
"event": "promote",
"status": "synced",
"skipReason": null,
"error": null,
"createdAt": "2026-06-27T12:01:00.000Z"
},
{
"id": "...",
"documentGroupId": "<doc-group-id>",
"notionPageId": null,
"event": "upload",
"status": "skipped",
"skipReason": "unchanged_hash",
"error": null,
"createdAt": "2026-06-27T11:55:00.000Z"
}
],
"summary": { "syncedLastHour": 7, "failedLastHour": 0, "skippedLastHour": 3 }
}Each row's event is what triggered the attempt (upload, promote, archive,
delete, backfill, reconcile); status is synced / skipped / failed.
For a skip, skipReason says why (unchanged_hash, proposed_only, disabled,
not_configured, drift_preserved, out_of_scope). For a failure, error
carries the message.
Configure
POST /notion-sync/:id/configureProvisions the target database and sets the policy — see
How to connect and
Configure options. Returns
{ "connection_id": "...", "config": { ... }, "backfill_enqueued": <n> }.
Backfill (force a full re-mirror)
POST /notion-sync/:id/backfillQueues every current document for re-mirroring. Unchanged documents are skipped
cheaply, so it is safe to run repeatedly. Returns
{ "connection_id": "...", "enqueued": <n> }. The sync must already be configured
(a parent page designated) or the call returns a 400.
Unsync (stop mirroring, keep the Notion pages)
POST /notion-sync/:id/unsyncDisables the sync and clears the page-mapping ledger so a later reconfigure starts
clean. Your existing Notion pages are left in place — their data is preserved;
this only stops future syncing. Returns
{ "connection_id": "...", "disabled": true, "mappings_cleared": <n> }. To remove
the connection entirely, use DELETE /connections/:id (see
Connecting data sources).
Via MCP
MCP clients see the Notion sync as three read-only resources. The writes (configure / backfill / unsync) are not on MCP by design — configuring where a workspace exports its knowledge base is workspace configuration, the same posture as connecting and disconnecting a data source. A human performs those from the console or your app calls the REST endpoints.
| Resource | What it returns |
|---|---|
notion_sync://<id> | The sync config + mirrored-document count. |
notion_sync://<id>/status | The live status: enabled, provisioned, provisioning state, mirrored count, recent-activity summary. |
notion_sync://<id>/sends | The recent per-document sync ledger (newest-first) + an hourly summary. |
notion_sync://<id>/pages | The Notion pages the integration can access (the parent-page picker). |
The notion_sync:// reads require the notion_sync:read scope.
Limits and notes
- Notion rate limit. Notion caps an integration at roughly 3 requests/second per workspace. The sync throttles its own request rate to stay under that ceiling; genuine rate-limit responses that slip through are retried with backoff. In practice this means a large backfill drains steadily rather than all at once.
- Large documents are chunked. Notion accepts at most 100 blocks per write, so a long document is appended in chunks. Over-long text runs are truncated to Notion's per-block limit rather than failing the whole document.
- Human edits are respected under
drift_policy: preserve. If someone edits the mirrored page in Notion after the last sync, apreservepolicy keeps their edit and records the skip asdrift_preserved; only anoverwritepolicy re-writes the page from Amdahl. The sync recognizes its own writes, so its own re-sync is never mistaken for a human edit. - Content-unchanged syncs are free. A re-sync of a document whose content has
not changed is skipped without touching Notion (
unchanged_hash). - One sync target per workspace. The Notion sync is single-instance — one outbound Notion connection per workspace.
- If Notion access is revoked, the connection flips to a needs-reauthorization state and syncing pauses; reconnect to resume (see Connection health and statuses).