{
  "connector": "notion",
  "label": "Notion",
  "vendor_docs": "https://developers.notion.com/reference/intro",
  "summary": "A Notion workspace that answers the REST API `notion-client` calls, Notion's public-integration OAuth, webhook events and Notion's MCP tools.",
  "surfaces": [
    {
      "kind": "REST",
      "path": "/v1",
      "notes": "pages, databases, data sources, blocks, comments, users, search, file uploads; `Notion-Version` header required"
    },
    {
      "kind": "OAuth",
      "path": "/v1/oauth",
      "notes": "`authorize`, `token`, `introspect`, `revoke` with Notion's token body"
    },
    {
      "kind": "Webhooks",
      "path": "webhook events",
      "notes": "delivered to a subscribed endpoint, retried with backoff"
    },
    {
      "kind": "MCP",
      "path": "/mcp",
      "notes": "bearer-authenticated, with RFC 9728 discovery for clients that register themselves"
    }
  ],
  "api_versions": [
    "Notion-Version 2022-06-28",
    "2025-09-03",
    "2026-03-11"
  ],
  "supported": [
    {
      "item": "Notion's list envelopes with `next_cursor`/`has_more`, and its error bodies"
    },
    {
      "item": "The shape changes of `Notion-Version` `2025-09-03` (data sources, `page_or_data_source`), and older clients seeing databases"
    },
    {
      "item": "The official SDK `notion-client`, with only `base_url` changed"
    },
    {
      "item": "Notion's own MCP server tool set"
    },
    {
      "item": "Connection capabilities from Notion's own set, enforced per token"
    },
    {
      "item": "The database query grammar"
    }
  ],
  "unsupported": [
    {
      "item": "Connected sources (Slack, Drive, Jira) in `notion-search`"
    },
    {
      "item": "A consent screen for OAuth; `authorize` auto-approves"
    },
    {
      "item": "Storage behind data-source templates"
    },
    {
      "item": "Fetching an upload's external URL"
    }
  ],
  "differences": [
    {
      "scenario": "Search ranking",
      "difference": "Search ranks by substring match, not Notion's relevance model.",
      "guidance": "Assert that a page is found, not on its position."
    },
    {
      "scenario": "Async tasks",
      "difference": "`allow_async` and `notion-duplicate-page` return real task handles, but the work is done before the handle is issued.",
      "guidance": "Do not assert on an in-progress state."
    },
    {
      "scenario": "Rate limits",
      "difference": "There is no 180 requests/minute limit; limits are only what you inject.",
      "guidance": "Inject `throttle` to test backoff."
    },
    {
      "scenario": "File URLs",
      "difference": "Upload bytes are held in the process and served from the replica's own signed `/v1/files/...` URL, not Notion's CDN.",
      "guidance": "Do not assert on the file host."
    },
    {
      "scenario": "Failing webhooks",
      "difference": "A refused delivery is retried with backoff, but a subscription is never disabled after repeated failures.",
      "guidance": "Do not test webhook auto-disable."
    },
    {
      "scenario": "Restarts",
      "difference": "Writes last until the process stops unless persistence is configured; upload bytes, webhook subscriptions and faults are never kept.",
      "guidance": "Treat each run as starting from the seeded workspace."
    }
  ],
  "faults_supported": [
    {
      "fault": "throttle",
      "behaviour": "429 `{\"object\": \"error\", \"code\": \"rate_limited\"}` with `Retry-After`"
    },
    {
      "fault": "fail_next",
      "behaviour": "The next N calls fail as `throttle` does, then clear"
    },
    {
      "fault": "quota_limit",
      "behaviour": "429 `rate_limited` \"Request quota exhausted\""
    },
    {
      "fault": "error_rate",
      "behaviour": "500 `internal_server_error` for the given fraction of calls"
    },
    {
      "fault": "latency_ms",
      "behaviour": "Fixed added latency on every data-plane request"
    }
  ]
}
