Docs

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:

code
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, and Last Synced properties 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 call configure with 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:

code
GET /notion-sync/:id/pages
json
{
  "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:

code
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.

OptionValuesDefaultWhat it controls
parent_page_ida Notion page id(required)The Notion page the synced database is created under.
version_policycurrent_only / all_promotedcurrent_onlyWhich versions to mirror. current_only mirrors just the promoted version (respects the human promotion gate).
on_kb_deletearchive / leavearchiveWhat happens to the Notion page when the source document is archived or deleted in Amdahl. archive trashes the page; leave keeps it.
drift_policypreserve / overwriteoverwriteWhat 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.
includescope filters (below)all current docsWhich documents are in scope.
enabledtrue / falsetrueMaster 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 KB document_type is 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

code
GET /notion-sync/:id

Returns the sync configuration plus how many documents are currently mirrored:

json
{
  "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)

code
GET /notion-sync/:id/status

A lean projection for polling a status badge or confirming a backfill is progressing:

json
{
  "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)

code
GET /notion-sync/:id/pages

Lists 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

code
GET /notion-sync/:id/sends

Outbound 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:

json
{
  "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

code
POST /notion-sync/:id/configure

Provisions 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)

code
POST /notion-sync/:id/backfill

Queues 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)

code
POST /notion-sync/:id/unsync

Disables 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.

ResourceWhat it returns
notion_sync://<id>The sync config + mirrored-document count.
notion_sync://<id>/statusThe live status: enabled, provisioned, provisioning state, mirrored count, recent-activity summary.
notion_sync://<id>/sendsThe recent per-document sync ledger (newest-first) + an hourly summary.
notion_sync://<id>/pagesThe 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, a preserve policy keeps their edit and records the skip as drift_preserved; only an overwrite policy 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).