diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,21 @@
+# Changelog for `notion-client-effectful`
+
+## 0.2.0.0 - 2026-09-15
+
+### Breaking Changes
+
+* Requires `notion-client >=0.8 && <0.9`.
+* `queryDataSource` and `search` now return `ListOf PageOrDataSource` (was `ListOf PageObject` and `ListOf Value`), following `notion-client`.
+* `updateBlock` takes `Blocks.BlockUpdatePayload` (was `Blocks.BlockUpdate`).
+* `createComment` returns `CommentResponse` (was `CommentObject`).
+* Remove `queryView` / `QueryView`; use `createViewQuery`, `getViewQueryResults` and `deleteViewQuery`.
+
+### New Features
+
+* New operations `createPageFiltered`, `updatePageFiltered`, `createPageAsync`, `updatePageMarkdownAsync`, `retrieveAsyncTask`, `retrieveComment`, `updateComment`, `deleteComment`, `createViewQuery`, `getViewQueryResults`, `deleteViewQuery`, `createMeetingNote` and `queryMeetingNotes`.
+
+## 0.1.0.0 - 2026-04-17
+
+* Initial release: `Notion` effect and `runNotion` interpreter covering
+  every `Notion.V1.Methods` field at the time of release
+  (notion-client 0.7.x).
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Nadeem Bitar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,115 @@
+# notion-client-effectful
+
+An [effectful][effectful] surface for
+[`notion-client`](https://hackage.haskell.org/package/notion-client).
+
+[effectful]: https://hackage.haskell.org/package/effectful
+
+This package wraps the `Notion.V1.Methods` record as a dynamic
+effect — so code written against `effectful` does not have to
+thread the `Methods` value through every call site and does not
+have to drop into `IO` at each API boundary.
+
+## Purpose
+
+`notion-client` exposes a single `Methods` record whose fields
+are `IO`-typed API calls. That is ergonomic for a plain-`IO`
+program but awkward for an `effectful`-based caller: every call
+requires `liftIO` and escapes the ambient `Error` / `Reader` /
+`Log` stack.
+
+`notion-client-effectful` gives that caller an equivalent set of
+operations surfaced as an `Effect` so the stack is preserved.
+
+## Import pattern
+
+Every smart constructor shares its name with the matching
+`Notion.V1.Methods` record selector. That is on purpose — it
+keeps migration from IO mechanical — but it means a file that
+imports both modules unqualified will see name clashes. Import
+one of the two qualified:
+
+```haskell
+import Notion.V1                    (Methods, getClientEnv, makeMethods)
+import Notion.V1.Effectful qualified as NE
+```
+
+Then `NE.retrievePage`, `NE.search`, `NE.runNotion`, and so on.
+
+## Error handling
+
+`runNotion` catches `Notion.V1.Error.NotionError` thrown by the
+underlying `Methods` and re-raises it via the `Error NotionError`
+effect, so callers can branch on API error shapes
+(`object_not_found`, validation failures, etc.) without reaching
+for `IO`-level exception handling.
+
+Other `Servant.Client.ClientError` values — network failures,
+decoding errors — are *not* caught and remain `IO` exceptions.
+This preserves the contract of the underlying `notion-client`
+library and lets callers layer their own `Error ClientError`
+interpretation on top later. The same applies to
+`UnknownHTTPResponseError`, `RequestTimeoutError` and
+`InvalidPathParameterError` from `Notion.V1.Error`. Retries of
+rate-limited requests happen inside `Methods`, before an error reaches
+`runNotion`.
+
+## Minimum viable usage
+
+```haskell
+module Demo where
+
+import Data.Text (Text)
+import Data.Text qualified as Text
+import Effectful (Eff, runEff, (:>))
+import Effectful.Error.Static (Error, runErrorNoCallStack)
+import Notion.V1                    (getClientEnv, makeMethods)
+import Notion.V1.Common             (UUID (..))
+import Notion.V1.Effectful qualified as NE
+import Notion.V1.Error              (NotionError)
+import System.Environment qualified as Env
+
+demo ::
+  (NE.Notion :> es, Error NotionError :> es) =>
+  UUID ->
+  Eff es Text
+demo pid = do
+  page <- NE.retrievePage pid
+  pure (Text.pack (show page))
+
+main :: IO ()
+main = do
+  token <- Text.pack <$> Env.getEnv "NOTION_TOKEN"
+  env <- getClientEnv "https://api.notion.com/v1"
+  let methods = makeMethods env token
+  result <-
+    runEff
+      . runErrorNoCallStack @NotionError
+      . NE.runNotion methods
+      $ demo (UUID "00000000-0000-0000-0000-000000000000")
+  print result
+```
+
+## What you get
+
+One smart constructor per field of `Notion.V1.Methods`. The
+argument types and order match the underlying field, so an
+existing call like
+
+```haskell
+page <- retrievePage methods pid
+```
+
+becomes
+
+```haskell
+page <- NE.retrievePage pid
+```
+
+with no other change to the call site.
+
+## Versioning
+
+The `0.y.z` series tracks `notion-client` 0.7.x. A major version
+bump is planned if `notion-client` changes the shape of `Methods`
+in a way that forces a constructor rename.
diff --git a/notion-client-effectful.cabal b/notion-client-effectful.cabal
new file mode 100644
--- /dev/null
+++ b/notion-client-effectful.cabal
@@ -0,0 +1,62 @@
+cabal-version: 3.4
+name: notion-client-effectful
+version: 0.2.0.0
+synopsis: Effectful effects for notion-client
+description:
+  Effectful effect + default interpreter for shinzui/notion-client.
+  Exposes every operation on 'Notion.V1.Methods' as a smart
+  constructor of the 'Notion.V1.Effectful.Notion' effect and provides
+  a 'runNotion' interpreter that dispatches through a concrete
+  @Methods@ value. API-level 'Notion.V1.Error.NotionError' responses
+  are surfaced via the @Error NotionError@ effect; other
+  @Servant.Client.ClientError@ values remain 'IO' exceptions.
+  See @README.md@ for the full import pattern and a worked example.
+
+license: MIT
+license-file: LICENSE
+author: Nadeem Bitar
+maintainer: nadeem@gmail.com
+category: Web
+build-type: Simple
+extra-doc-files:
+  CHANGELOG.md
+  README.md
+
+common warnings
+  ghc-options:
+    -Wall
+    -Wcompat
+    -Widentities
+    -Wincomplete-uni-patterns
+    -Wincomplete-record-updates
+    -Wredundant-constraints
+    -fhide-source-paths
+    -Wmissing-export-lists
+    -Wpartial-fields
+    -Wmissing-deriving-strategies
+
+library
+  import: warnings
+  exposed-modules:
+    Notion.V1.Effectful
+    Notion.V1.Effectful.Effect
+    Notion.V1.Effectful.Interpreter
+
+  default-extensions:
+    DataKinds
+    DuplicateRecordFields
+    GADTs
+    LambdaCase
+    OverloadedStrings
+    TypeFamilies
+    TypeOperators
+
+  build-depends:
+    aeson >=2.2 && <2.3,
+    base >=4.18 && <5,
+    effectful-core >=2.5 && <3,
+    notion-client >=0.8 && <0.9,
+    text >=2.0 && <2.2,
+
+  hs-source-dirs: src
+  default-language: GHC2024
diff --git a/src/Notion/V1/Effectful.hs b/src/Notion/V1/Effectful.hs
new file mode 100644
--- /dev/null
+++ b/src/Notion/V1/Effectful.hs
@@ -0,0 +1,137 @@
+-- | Effectful surface for @notion-client@.
+--
+-- This module re-exports the 'Notion' effect, its smart constructors,
+-- and the default 'runNotion' interpreter. A caller typically writes:
+--
+-- @
+-- import Notion.V1                    (Methods, getClientEnv, makeMethods)
+-- import Notion.V1.Effectful qualified as NE
+-- @
+--
+-- and then uses @NE.retrievePage@, @NE.search@, @NE.runNotion@, etc.
+-- The qualified import avoids name clashes with the matching
+-- 'Notion.V1.Methods' record selectors — every smart constructor
+-- here shares its name with a field of @Methods@ on purpose, so
+-- migrating an IO call site is a near-mechanical rewrite.
+--
+-- Error handling: 'runNotion' catches 'NotionError' from the
+-- underlying 'Methods' value and re-throws via the @Error NotionError@
+-- effect. Other 'Servant.Client.ClientError' values (network
+-- failures, decoding errors) remain 'IO' exceptions.
+module Notion.V1.Effectful
+  ( -- * Effect
+    Notion,
+
+    -- * Interpreter
+    runNotion,
+
+    -- * Databases
+    createDatabase,
+    retrieveDatabase,
+    updateDatabase,
+    queryDatabase,
+
+    -- * Data Sources
+    retrieveDataSource,
+    createDataSource,
+    updateDataSource,
+    queryDataSource,
+    listDataSourceTemplates,
+
+    -- * Pages
+    createPage,
+    retrievePage,
+    retrievePageFiltered,
+    updatePage,
+    retrievePageProperty,
+    retrievePageMarkdown,
+    updatePageMarkdown,
+    movePage,
+
+    -- * Blocks
+    retrieveBlock,
+    updateBlock,
+    listBlockChildren,
+    appendBlockChildren,
+    deleteBlock,
+
+    -- * Users
+    retrieveUser,
+    listUsers,
+    retrieveMyUser,
+
+    -- * Search
+    search,
+
+    -- * Comments
+    createComment,
+    listComments,
+
+    -- * Views
+    createView,
+    retrieveView,
+    updateView,
+    deleteView,
+    listViews,
+    createViewQuery,
+    getViewQueryResults,
+    deleteViewQuery,
+
+    -- * Custom Emojis
+    listCustomEmojis,
+
+    -- * File Uploads
+    createFileUpload,
+    retrieveFileUpload,
+    sendFileUploadContent,
+    completeFileUpload,
+    listFileUploads,
+  )
+where
+
+import Notion.V1.Effectful.Effect
+  ( Notion,
+    appendBlockChildren,
+    completeFileUpload,
+    createComment,
+    createDataSource,
+    createDatabase,
+    createFileUpload,
+    createPage,
+    createView,
+    createViewQuery,
+    deleteBlock,
+    deleteView,
+    deleteViewQuery,
+    getViewQueryResults,
+    listBlockChildren,
+    listComments,
+    listCustomEmojis,
+    listDataSourceTemplates,
+    listFileUploads,
+    listUsers,
+    listViews,
+    movePage,
+    queryDataSource,
+    queryDatabase,
+    retrieveBlock,
+    retrieveDataSource,
+    retrieveDatabase,
+    retrieveFileUpload,
+    retrieveMyUser,
+    retrievePage,
+    retrievePageFiltered,
+    retrievePageMarkdown,
+    retrievePageProperty,
+    retrieveUser,
+    retrieveView,
+    search,
+    sendFileUploadContent,
+    updateBlock,
+    updateDataSource,
+    updateDatabase,
+    updatePage,
+    updatePageMarkdown,
+    updateView,
+  )
+import Notion.V1.Effectful.Interpreter (runNotion)
diff --git a/src/Notion/V1/Effectful/Effect.hs b/src/Notion/V1/Effectful/Effect.hs
new file mode 100644
--- /dev/null
+++ b/src/Notion/V1/Effectful/Effect.hs
@@ -0,0 +1,534 @@
+-- | The 'Notion' effect and its smart constructors.
+--
+-- One constructor per field of 'Notion.V1.Methods'. Each smart
+-- constructor has the same name, same argument order, and same
+-- argument types as the corresponding @Methods@ field — so migrating
+-- an 'IO'-based call site amounts to dropping the explicit
+-- @methods@ argument.
+--
+-- /Import note./ Every smart constructor name clashes with the
+-- matching 'Notion.V1.Methods' record selector. Import one of the
+-- two modules qualified (mirroring how 'Notion.V1' itself qualifies
+-- @Pages@, @Databases@, etc.).
+module Notion.V1.Effectful.Effect
+  ( -- * Effect
+    Notion (..),
+
+    -- * Databases
+    createDatabase,
+    retrieveDatabase,
+    updateDatabase,
+    queryDatabase,
+
+    -- * Data Sources
+    retrieveDataSource,
+    createDataSource,
+    updateDataSource,
+    queryDataSource,
+    listDataSourceTemplates,
+
+    -- * Pages
+    createPage,
+    createPageFiltered,
+    retrievePage,
+    retrievePageFiltered,
+    updatePage,
+    updatePageFiltered,
+    retrievePageProperty,
+    retrievePageMarkdown,
+    updatePageMarkdown,
+    movePage,
+    createPageAsync,
+    updatePageMarkdownAsync,
+
+    -- * Blocks
+    retrieveBlock,
+    updateBlock,
+    listBlockChildren,
+    appendBlockChildren,
+    deleteBlock,
+
+    -- * Users
+    retrieveUser,
+    listUsers,
+    retrieveMyUser,
+
+    -- * Search
+    search,
+
+    -- * Comments
+    createComment,
+    listComments,
+    retrieveComment,
+    updateComment,
+    deleteComment,
+
+    -- * Views
+    createView,
+    retrieveView,
+    updateView,
+    deleteView,
+    listViews,
+    createViewQuery,
+    getViewQueryResults,
+    deleteViewQuery,
+
+    -- * Custom Emojis
+    listCustomEmojis,
+
+    -- * File Uploads
+    createFileUpload,
+    retrieveFileUpload,
+    sendFileUploadContent,
+    completeFileUpload,
+    listFileUploads,
+
+    -- * Async Tasks
+    retrieveAsyncTask,
+
+    -- * Meeting Notes
+    createMeetingNote,
+    queryMeetingNotes,
+  )
+where
+
+import Data.Text (Text)
+import Effectful (Dispatch (..), DispatchOf, Eff, Effect, (:>))
+import Effectful.Dispatch.Dynamic (send)
+import Notion.V1.AsyncTasks (AsyncOr, AsyncTask, AsyncTaskID)
+import Notion.V1.Blocks (BlockID, BlockObject)
+import Notion.V1.Blocks qualified as Blocks
+import Notion.V1.Comments (CommentObject, CommentResponse)
+import Notion.V1.Comments qualified as Comments
+import Notion.V1.Common (ParentID, UUID)
+import Notion.V1.CustomEmojis (CustomEmoji)
+import Notion.V1.DataSources (DataSourceID, DataSourceObject)
+import Notion.V1.DataSources qualified as DataSources
+import Notion.V1.Databases (CreateDatabase, DatabaseID, DatabaseObject, QueryDatabase, UpdateDatabase)
+import Notion.V1.FileUploads (FileUploadID, FileUploadObject, FileUploadStatus)
+import Notion.V1.FileUploads qualified as FileUploads
+import Notion.V1.ListOf (ListOf)
+import Notion.V1.MeetingNotes qualified as MeetingNotes
+import Notion.V1.Pages (CreatePage, MovePage, PageID, PageMarkdown, PageObject, PartialPageObject, PropertyItemResponse, UpdatePage, UpdatePageMarkdown)
+import Notion.V1.Search (PageOrDataSource, SearchRequest)
+import Notion.V1.Users (UserID, UserObject)
+import Notion.V1.Views (ViewObject)
+import Notion.V1.Views qualified as Views
+import Numeric.Natural (Natural)
+
+-- | Operations on a Notion workspace.
+--
+-- One constructor per field of 'Notion.V1.Methods'. The constructor
+-- names are the @PascalCase@ form of the field names; argument order
+-- matches the field signature.
+data Notion :: Effect where
+  -- Databases
+  CreateDatabase :: CreateDatabase -> Notion m DatabaseObject
+  RetrieveDatabase :: DatabaseID -> Notion m DatabaseObject
+  UpdateDatabase :: DatabaseID -> UpdateDatabase -> Notion m DatabaseObject
+  QueryDatabase :: DatabaseID -> QueryDatabase -> Notion m (ListOf PageObject)
+  -- Data Sources
+  RetrieveDataSource :: DataSourceID -> Notion m DataSourceObject
+  CreateDataSource :: DataSources.CreateDataSource -> Notion m DataSourceObject
+  UpdateDataSource :: DataSourceID -> DataSources.UpdateDataSource -> Notion m DataSourceObject
+  QueryDataSource :: DataSourceID -> DataSources.QueryDataSource -> Notion m (ListOf DataSources.PageOrDataSource)
+  ListDataSourceTemplates ::
+    DataSourceID ->
+    Maybe Text ->
+    Maybe Text ->
+    Maybe Natural ->
+    Notion m DataSources.ListTemplatesResponse
+  -- Pages
+  CreatePage :: CreatePage -> Notion m PageObject
+  CreatePageFiltered :: [Text] -> CreatePage -> Notion m PageObject
+  RetrievePage :: PageID -> Notion m PageObject
+  RetrievePageFiltered :: PageID -> [Text] -> Notion m PageObject
+  UpdatePage :: PageID -> UpdatePage -> Notion m PageObject
+  UpdatePageFiltered :: PageID -> [Text] -> UpdatePage -> Notion m PageObject
+  RetrievePageProperty ::
+    PageID ->
+    Text ->
+    Maybe Text ->
+    Maybe Natural ->
+    Notion m PropertyItemResponse
+  RetrievePageMarkdown :: PageID -> Maybe Bool -> Notion m PageMarkdown
+  UpdatePageMarkdown :: PageID -> UpdatePageMarkdown -> Notion m PageMarkdown
+  MovePage :: PageID -> MovePage -> Notion m PageObject
+  CreatePageAsync :: CreatePage -> Notion m (AsyncOr PageObject)
+  UpdatePageMarkdownAsync :: PageID -> UpdatePageMarkdown -> Notion m (AsyncOr PageMarkdown)
+  -- Blocks
+  RetrieveBlock :: BlockID -> Notion m BlockObject
+  UpdateBlock :: BlockID -> Blocks.BlockUpdatePayload -> Notion m BlockObject
+  ListBlockChildren :: ParentID -> Maybe Natural -> Maybe Text -> Notion m (ListOf BlockObject)
+  AppendBlockChildren :: ParentID -> Blocks.AppendBlockChildren -> Notion m (ListOf BlockObject)
+  DeleteBlock :: BlockID -> Notion m BlockObject
+  -- Users
+  RetrieveUser :: UserID -> Notion m UserObject
+  ListUsers :: Maybe Natural -> Maybe Text -> Notion m (ListOf UserObject)
+  RetrieveMyUser :: Notion m UserObject
+  -- Search
+  Search :: SearchRequest -> Notion m (ListOf PageOrDataSource)
+  -- Comments
+  CreateComment :: Comments.CreateComment -> Notion m CommentResponse
+  ListComments :: Maybe BlockID -> Maybe Text -> Maybe Natural -> Notion m (ListOf CommentObject)
+  RetrieveComment :: Comments.CommentID -> Notion m CommentResponse
+  UpdateComment :: Comments.CommentID -> Comments.CommentContent -> Notion m CommentResponse
+  DeleteComment :: Comments.CommentID -> Notion m CommentResponse
+  -- Views
+  CreateView :: Views.CreateView -> Notion m ViewObject
+  RetrieveView :: Views.ViewID -> Notion m ViewObject
+  UpdateView :: Views.ViewID -> Views.UpdateView -> Notion m ViewObject
+  DeleteView :: Views.ViewID -> Notion m ViewObject
+  ListViews ::
+    Maybe UUID ->
+    Maybe UUID ->
+    Maybe Text ->
+    Maybe Natural ->
+    Notion m (ListOf ViewObject)
+  CreateViewQuery :: Views.ViewID -> Views.CreateViewQuery -> Notion m Views.ViewQuery
+  GetViewQueryResults :: Views.ViewID -> Views.ViewQueryID -> Maybe Text -> Maybe Natural -> Notion m (ListOf PartialPageObject)
+  DeleteViewQuery :: Views.ViewID -> Views.ViewQueryID -> Notion m Views.DeletedViewQuery
+  -- Custom Emojis
+  ListCustomEmojis ::
+    Maybe Text ->
+    Maybe Text ->
+    Maybe Natural ->
+    Notion m (ListOf CustomEmoji)
+  -- File Uploads
+  CreateFileUpload :: FileUploads.CreateFileUpload -> Notion m FileUploadObject
+  RetrieveFileUpload :: FileUploadID -> Notion m FileUploadObject
+  SendFileUploadContent :: FileUploadID -> FileUploads.SendFileUpload -> Notion m FileUploadObject
+  CompleteFileUpload :: FileUploadID -> Notion m FileUploadObject
+  ListFileUploads ::
+    Maybe FileUploadStatus ->
+    Maybe Text ->
+    Maybe Natural ->
+    Notion m (ListOf FileUploadObject)
+  -- Async Tasks
+  RetrieveAsyncTask :: AsyncTaskID -> Notion m AsyncTask
+  -- Meeting Notes
+  CreateMeetingNote :: MeetingNotes.CreateMeetingNote -> Notion m MeetingNotes.CreateMeetingNoteResponse
+  QueryMeetingNotes :: MeetingNotes.QueryMeetingNotes -> Notion m MeetingNotes.QueryMeetingNotesResponse
+
+type instance DispatchOf Notion = 'Dynamic
+
+-- ── Databases ─────────────────────────────────────────────────────
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.createDatabase'.
+createDatabase :: (Notion :> es) => CreateDatabase -> Eff es DatabaseObject
+createDatabase = send . CreateDatabase
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.retrieveDatabase'.
+retrieveDatabase :: (Notion :> es) => DatabaseID -> Eff es DatabaseObject
+retrieveDatabase = send . RetrieveDatabase
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.updateDatabase'.
+updateDatabase :: (Notion :> es) => DatabaseID -> UpdateDatabase -> Eff es DatabaseObject
+updateDatabase dbId upd = send (UpdateDatabase dbId upd)
+
+{-# DEPRECATED queryDatabase "Use 'queryDataSource' instead." #-}
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.queryDatabase'. Deprecated upstream;
+-- the constructor is retained for parity so existing call sites can migrate
+-- in lockstep with the base library.
+queryDatabase ::
+  (Notion :> es) =>
+  DatabaseID ->
+  QueryDatabase ->
+  Eff es (ListOf PageObject)
+queryDatabase dbId q = send (QueryDatabase dbId q)
+
+-- ── Data Sources ──────────────────────────────────────────────────
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.retrieveDataSource'.
+retrieveDataSource :: (Notion :> es) => DataSourceID -> Eff es DataSourceObject
+retrieveDataSource = send . RetrieveDataSource
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.createDataSource'.
+createDataSource :: (Notion :> es) => DataSources.CreateDataSource -> Eff es DataSourceObject
+createDataSource = send . CreateDataSource
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.updateDataSource'.
+updateDataSource ::
+  (Notion :> es) =>
+  DataSourceID ->
+  DataSources.UpdateDataSource ->
+  Eff es DataSourceObject
+updateDataSource dsId upd = send (UpdateDataSource dsId upd)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.queryDataSource'.
+queryDataSource ::
+  (Notion :> es) =>
+  DataSourceID ->
+  DataSources.QueryDataSource ->
+  Eff es (ListOf DataSources.PageOrDataSource)
+queryDataSource dsId q = send (QueryDataSource dsId q)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.listDataSourceTemplates'.
+listDataSourceTemplates ::
+  (Notion :> es) =>
+  DataSourceID ->
+  Maybe Text ->
+  Maybe Text ->
+  Maybe Natural ->
+  Eff es DataSources.ListTemplatesResponse
+listDataSourceTemplates dsId nameFilter startCursor pageSize =
+  send (ListDataSourceTemplates dsId nameFilter startCursor pageSize)
+
+-- ── Pages ─────────────────────────────────────────────────────────
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.createPage'.
+createPage :: (Notion :> es) => CreatePage -> Eff es PageObject
+createPage = send . CreatePage
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.createPageFiltered'.
+createPageFiltered :: (Notion :> es) => [Text] -> CreatePage -> Eff es PageObject
+createPageFiltered props req = send (CreatePageFiltered props req)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.retrievePage'.
+retrievePage :: (Notion :> es) => PageID -> Eff es PageObject
+retrievePage = send . RetrievePage
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.retrievePageFiltered'.
+retrievePageFiltered :: (Notion :> es) => PageID -> [Text] -> Eff es PageObject
+retrievePageFiltered pid props = send (RetrievePageFiltered pid props)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.updatePage'.
+updatePage :: (Notion :> es) => PageID -> UpdatePage -> Eff es PageObject
+updatePage pid upd = send (UpdatePage pid upd)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.updatePageFiltered'.
+updatePageFiltered :: (Notion :> es) => PageID -> [Text] -> UpdatePage -> Eff es PageObject
+updatePageFiltered pid props upd = send (UpdatePageFiltered pid props upd)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.retrievePageProperty'.
+retrievePageProperty ::
+  (Notion :> es) =>
+  PageID ->
+  Text ->
+  Maybe Text ->
+  Maybe Natural ->
+  Eff es PropertyItemResponse
+retrievePageProperty pid prop cursor size =
+  send (RetrievePageProperty pid prop cursor size)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.retrievePageMarkdown'.
+retrievePageMarkdown :: (Notion :> es) => PageID -> Maybe Bool -> Eff es PageMarkdown
+retrievePageMarkdown pid includeTx = send (RetrievePageMarkdown pid includeTx)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.updatePageMarkdown'.
+updatePageMarkdown :: (Notion :> es) => PageID -> UpdatePageMarkdown -> Eff es PageMarkdown
+updatePageMarkdown pid upd = send (UpdatePageMarkdown pid upd)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.movePage'.
+movePage :: (Notion :> es) => PageID -> MovePage -> Eff es PageObject
+movePage pid mv = send (MovePage pid mv)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.createPageAsync'.
+createPageAsync :: (Notion :> es) => CreatePage -> Eff es (AsyncOr PageObject)
+createPageAsync = send . CreatePageAsync
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.updatePageMarkdownAsync'.
+updatePageMarkdownAsync ::
+  (Notion :> es) =>
+  PageID ->
+  UpdatePageMarkdown ->
+  Eff es (AsyncOr PageMarkdown)
+updatePageMarkdownAsync pid upd = send (UpdatePageMarkdownAsync pid upd)
+
+-- ── Blocks ────────────────────────────────────────────────────────
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.retrieveBlock'.
+retrieveBlock :: (Notion :> es) => BlockID -> Eff es BlockObject
+retrieveBlock = send . RetrieveBlock
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.updateBlock'.
+updateBlock :: (Notion :> es) => BlockID -> Blocks.BlockUpdatePayload -> Eff es BlockObject
+updateBlock bid upd = send (UpdateBlock bid upd)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.listBlockChildren'.
+listBlockChildren ::
+  (Notion :> es) =>
+  ParentID ->
+  Maybe Natural ->
+  Maybe Text ->
+  Eff es (ListOf BlockObject)
+listBlockChildren pid pageSize startCursor =
+  send (ListBlockChildren pid pageSize startCursor)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.appendBlockChildren'.
+appendBlockChildren ::
+  (Notion :> es) =>
+  ParentID ->
+  Blocks.AppendBlockChildren ->
+  Eff es (ListOf BlockObject)
+appendBlockChildren pid append = send (AppendBlockChildren pid append)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.deleteBlock'.
+deleteBlock :: (Notion :> es) => BlockID -> Eff es BlockObject
+deleteBlock = send . DeleteBlock
+
+-- ── Users ─────────────────────────────────────────────────────────
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.retrieveUser'.
+retrieveUser :: (Notion :> es) => UserID -> Eff es UserObject
+retrieveUser = send . RetrieveUser
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.listUsers'.
+listUsers :: (Notion :> es) => Maybe Natural -> Maybe Text -> Eff es (ListOf UserObject)
+listUsers pageSize startCursor = send (ListUsers pageSize startCursor)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.retrieveMyUser'.
+retrieveMyUser :: (Notion :> es) => Eff es UserObject
+retrieveMyUser = send RetrieveMyUser
+
+-- ── Search ────────────────────────────────────────────────────────
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.search'.
+search :: (Notion :> es) => SearchRequest -> Eff es (ListOf PageOrDataSource)
+search = send . Search
+
+-- ── Comments ──────────────────────────────────────────────────────
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.createComment'.
+createComment :: (Notion :> es) => Comments.CreateComment -> Eff es CommentResponse
+createComment = send . CreateComment
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.listComments'.
+listComments ::
+  (Notion :> es) =>
+  Maybe BlockID ->
+  Maybe Text ->
+  Maybe Natural ->
+  Eff es (ListOf CommentObject)
+listComments bid startCursor pageSize = send (ListComments bid startCursor pageSize)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.retrieveComment'.
+retrieveComment :: (Notion :> es) => Comments.CommentID -> Eff es CommentResponse
+retrieveComment = send . RetrieveComment
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.updateComment'.
+updateComment ::
+  (Notion :> es) =>
+  Comments.CommentID ->
+  Comments.CommentContent ->
+  Eff es CommentResponse
+updateComment cid content = send (UpdateComment cid content)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.deleteComment'.
+deleteComment :: (Notion :> es) => Comments.CommentID -> Eff es CommentResponse
+deleteComment = send . DeleteComment
+
+-- ── Views ─────────────────────────────────────────────────────────
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.createView'.
+createView :: (Notion :> es) => Views.CreateView -> Eff es ViewObject
+createView = send . CreateView
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.retrieveView'.
+retrieveView :: (Notion :> es) => Views.ViewID -> Eff es ViewObject
+retrieveView = send . RetrieveView
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.updateView'.
+updateView :: (Notion :> es) => Views.ViewID -> Views.UpdateView -> Eff es ViewObject
+updateView vid upd = send (UpdateView vid upd)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.deleteView'.
+deleteView :: (Notion :> es) => Views.ViewID -> Eff es ViewObject
+deleteView = send . DeleteView
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.listViews'.
+listViews ::
+  (Notion :> es) =>
+  Maybe UUID ->
+  Maybe UUID ->
+  Maybe Text ->
+  Maybe Natural ->
+  Eff es (ListOf ViewObject)
+listViews dbId dsId startCursor pageSize =
+  send (ListViews dbId dsId startCursor pageSize)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.createViewQuery'.
+createViewQuery :: (Notion :> es) => Views.ViewID -> Views.CreateViewQuery -> Eff es Views.ViewQuery
+createViewQuery vid req = send (CreateViewQuery vid req)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.getViewQueryResults'.
+getViewQueryResults ::
+  (Notion :> es) =>
+  Views.ViewID ->
+  Views.ViewQueryID ->
+  Maybe Text ->
+  Maybe Natural ->
+  Eff es (ListOf PartialPageObject)
+getViewQueryResults vid qid cursor size = send (GetViewQueryResults vid qid cursor size)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.deleteViewQuery'.
+deleteViewQuery :: (Notion :> es) => Views.ViewID -> Views.ViewQueryID -> Eff es Views.DeletedViewQuery
+deleteViewQuery vid qid = send (DeleteViewQuery vid qid)
+
+-- ── Custom Emojis ─────────────────────────────────────────────────
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.listCustomEmojis'.
+listCustomEmojis ::
+  (Notion :> es) =>
+  Maybe Text ->
+  Maybe Text ->
+  Maybe Natural ->
+  Eff es (ListOf CustomEmoji)
+listCustomEmojis nameFilter startCursor pageSize =
+  send (ListCustomEmojis nameFilter startCursor pageSize)
+
+-- ── File Uploads ──────────────────────────────────────────────────
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.createFileUpload'.
+createFileUpload :: (Notion :> es) => FileUploads.CreateFileUpload -> Eff es FileUploadObject
+createFileUpload = send . CreateFileUpload
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.retrieveFileUpload'.
+retrieveFileUpload :: (Notion :> es) => FileUploadID -> Eff es FileUploadObject
+retrieveFileUpload = send . RetrieveFileUpload
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.sendFileUploadContent'.
+sendFileUploadContent ::
+  (Notion :> es) =>
+  FileUploadID ->
+  FileUploads.SendFileUpload ->
+  Eff es FileUploadObject
+sendFileUploadContent fid payload = send (SendFileUploadContent fid payload)
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.completeFileUpload'.
+completeFileUpload :: (Notion :> es) => FileUploadID -> Eff es FileUploadObject
+completeFileUpload = send . CompleteFileUpload
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.listFileUploads'.
+listFileUploads ::
+  (Notion :> es) =>
+  Maybe FileUploadStatus ->
+  Maybe Text ->
+  Maybe Natural ->
+  Eff es (ListOf FileUploadObject)
+listFileUploads statusFilter startCursor pageSize =
+  send (ListFileUploads statusFilter startCursor pageSize)
+
+-- ── Async Tasks ───────────────────────────────────────────────────
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.retrieveAsyncTask'. Pass it to
+-- 'Notion.V1.AsyncTasks.waitForAsyncTask' to poll from 'Eff'.
+retrieveAsyncTask :: (Notion :> es) => AsyncTaskID -> Eff es AsyncTask
+retrieveAsyncTask = send . RetrieveAsyncTask
+
+-- ── Meeting Notes ─────────────────────────────────────────────────
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.createMeetingNote'.
+createMeetingNote ::
+  (Notion :> es) =>
+  MeetingNotes.CreateMeetingNote ->
+  Eff es MeetingNotes.CreateMeetingNoteResponse
+createMeetingNote = send . CreateMeetingNote
+
+-- | See 'Notion.V1.Methods'.'Notion.V1.queryMeetingNotes'.
+queryMeetingNotes ::
+  (Notion :> es) =>
+  MeetingNotes.QueryMeetingNotes ->
+  Eff es MeetingNotes.QueryMeetingNotesResponse
+queryMeetingNotes = send . QueryMeetingNotes
diff --git a/src/Notion/V1/Effectful/Interpreter.hs b/src/Notion/V1/Effectful/Interpreter.hs
new file mode 100644
--- /dev/null
+++ b/src/Notion/V1/Effectful/Interpreter.hs
@@ -0,0 +1,179 @@
+-- | Default interpreter for the 'Notion' effect.
+--
+-- 'runNotion' dispatches every 'Notion' constructor through a
+-- concrete 'Notion.V1.Methods' value. Any 'NotionError' thrown by
+-- the underlying 'IO' action is caught and re-raised via the
+-- 'Error' effect, so callers can branch on Notion API error shapes
+-- (e.g. @object_not_found@) without resorting to 'IO'-level exception
+-- handling. Other 'Servant.Client.ClientError' values (network
+-- failures, decoding errors) are intentionally /not/ caught: they
+-- remain 'IO' exceptions, preserving the existing 'Notion.V1' contract.
+-- The same holds for 'Notion.V1.Error.UnknownHTTPResponseError',
+-- 'Notion.V1.Error.RequestTimeoutError' and
+-- 'Notion.V1.Error.InvalidPathParameterError'. Retries happen inside the
+-- 'Notion.V1.Methods' value, before an error reaches the interpreter.
+module Notion.V1.Effectful.Interpreter
+  ( runNotion,
+  )
+where
+
+import Control.Exception qualified as Exception
+import Effectful (Eff, IOE, liftIO, (:>))
+import Effectful.Dispatch.Dynamic (interpret)
+import Effectful.Error.Static (Error, throwError)
+import Notion.V1 (Methods)
+import Notion.V1 qualified as Notion
+import Notion.V1.Effectful.Effect
+  ( Notion
+      ( AppendBlockChildren,
+        CompleteFileUpload,
+        CreateComment,
+        CreateDataSource,
+        CreateDatabase,
+        CreateFileUpload,
+        CreateMeetingNote,
+        CreatePage,
+        CreatePageAsync,
+        CreatePageFiltered,
+        CreateView,
+        CreateViewQuery,
+        DeleteBlock,
+        DeleteComment,
+        DeleteView,
+        DeleteViewQuery,
+        GetViewQueryResults,
+        ListBlockChildren,
+        ListComments,
+        ListCustomEmojis,
+        ListDataSourceTemplates,
+        ListFileUploads,
+        ListUsers,
+        ListViews,
+        MovePage,
+        QueryDataSource,
+        QueryDatabase,
+        QueryMeetingNotes,
+        RetrieveAsyncTask,
+        RetrieveBlock,
+        RetrieveComment,
+        RetrieveDataSource,
+        RetrieveDatabase,
+        RetrieveFileUpload,
+        RetrieveMyUser,
+        RetrievePage,
+        RetrievePageFiltered,
+        RetrievePageMarkdown,
+        RetrievePageProperty,
+        RetrieveUser,
+        RetrieveView,
+        Search,
+        SendFileUploadContent,
+        UpdateBlock,
+        UpdateComment,
+        UpdateDataSource,
+        UpdateDatabase,
+        UpdatePage,
+        UpdatePageFiltered,
+        UpdatePageMarkdown,
+        UpdatePageMarkdownAsync,
+        UpdateView
+      ),
+  )
+import Notion.V1.Error (NotionError)
+
+-- | Interpret 'Notion' using a concrete 'Methods' value.
+--
+-- >>> runEff . runErrorNoCallStack @NotionError . runNotion methods $ retrievePage pid
+--
+-- The dispatched 'IO' action is wrapped with 'Control.Exception.try':
+-- a 'NotionError' is re-thrown via 'throwError', anything else
+-- propagates as an 'IO' exception.
+runNotion ::
+  (IOE :> es, Error NotionError :> es) =>
+  Methods ->
+  Eff (Notion : es) a ->
+  Eff es a
+runNotion methods = interpret $ \_ -> \case
+  -- Databases
+  CreateDatabase req -> runIO (Notion.createDatabase methods req)
+  RetrieveDatabase dbId -> runIO (Notion.retrieveDatabase methods dbId)
+  UpdateDatabase dbId req -> runIO (Notion.updateDatabase methods dbId req)
+  QueryDatabase dbId req -> runIO (Notion.queryDatabase methods dbId req)
+  -- Data Sources
+  RetrieveDataSource dsId -> runIO (Notion.retrieveDataSource methods dsId)
+  CreateDataSource req -> runIO (Notion.createDataSource methods req)
+  UpdateDataSource dsId req -> runIO (Notion.updateDataSource methods dsId req)
+  QueryDataSource dsId req -> runIO (Notion.queryDataSource methods dsId req)
+  ListDataSourceTemplates dsId nameFilter cursor pageSize ->
+    runIO (Notion.listDataSourceTemplates methods dsId nameFilter cursor pageSize)
+  -- Pages
+  CreatePage req -> runIO (Notion.createPage methods req)
+  CreatePageFiltered props req -> runIO (Notion.createPageFiltered methods props req)
+  RetrievePage pid -> runIO (Notion.retrievePage methods pid)
+  RetrievePageFiltered pid props -> runIO (Notion.retrievePageFiltered methods pid props)
+  UpdatePage pid req -> runIO (Notion.updatePage methods pid req)
+  UpdatePageFiltered pid props req -> runIO (Notion.updatePageFiltered methods pid props req)
+  RetrievePageProperty pid prop cursor size ->
+    runIO (Notion.retrievePageProperty methods pid prop cursor size)
+  RetrievePageMarkdown pid includeTx ->
+    runIO (Notion.retrievePageMarkdown methods pid includeTx)
+  UpdatePageMarkdown pid req -> runIO (Notion.updatePageMarkdown methods pid req)
+  MovePage pid req -> runIO (Notion.movePage methods pid req)
+  CreatePageAsync req -> runIO (Notion.createPageAsync methods req)
+  UpdatePageMarkdownAsync pid req -> runIO (Notion.updatePageMarkdownAsync methods pid req)
+  -- Blocks
+  RetrieveBlock bid -> runIO (Notion.retrieveBlock methods bid)
+  UpdateBlock bid req -> runIO (Notion.updateBlock methods bid req)
+  ListBlockChildren pid pageSize cursor ->
+    runIO (Notion.listBlockChildren methods pid pageSize cursor)
+  AppendBlockChildren pid req -> runIO (Notion.appendBlockChildren methods pid req)
+  DeleteBlock bid -> runIO (Notion.deleteBlock methods bid)
+  -- Users
+  RetrieveUser uid -> runIO (Notion.retrieveUser methods uid)
+  ListUsers pageSize cursor -> runIO (Notion.listUsers methods pageSize cursor)
+  RetrieveMyUser -> runIO (Notion.retrieveMyUser methods)
+  -- Search
+  Search req -> runIO (Notion.search methods req)
+  -- Comments
+  CreateComment req -> runIO (Notion.createComment methods req)
+  ListComments bid cursor pageSize ->
+    runIO (Notion.listComments methods bid cursor pageSize)
+  RetrieveComment cid -> runIO (Notion.retrieveComment methods cid)
+  UpdateComment cid content -> runIO (Notion.updateComment methods cid content)
+  DeleteComment cid -> runIO (Notion.deleteComment methods cid)
+  -- Views
+  CreateView req -> runIO (Notion.createView methods req)
+  RetrieveView vid -> runIO (Notion.retrieveView methods vid)
+  UpdateView vid req -> runIO (Notion.updateView methods vid req)
+  DeleteView vid -> runIO (Notion.deleteView methods vid)
+  ListViews dbId dsId cursor pageSize ->
+    runIO (Notion.listViews methods dbId dsId cursor pageSize)
+  CreateViewQuery vid req -> runIO (Notion.createViewQuery methods vid req)
+  GetViewQueryResults vid qid cursor size ->
+    runIO (Notion.getViewQueryResults methods vid qid cursor size)
+  DeleteViewQuery vid qid -> runIO (Notion.deleteViewQuery methods vid qid)
+  -- Custom Emojis
+  ListCustomEmojis nameFilter cursor pageSize ->
+    runIO (Notion.listCustomEmojis methods nameFilter cursor pageSize)
+  -- File Uploads
+  CreateFileUpload req -> runIO (Notion.createFileUpload methods req)
+  RetrieveFileUpload fid -> runIO (Notion.retrieveFileUpload methods fid)
+  SendFileUploadContent fid payload ->
+    runIO (Notion.sendFileUploadContent methods fid payload)
+  CompleteFileUpload fid -> runIO (Notion.completeFileUpload methods fid)
+  ListFileUploads statusFilter cursor pageSize ->
+    runIO (Notion.listFileUploads methods statusFilter cursor pageSize)
+  -- Async Tasks
+  RetrieveAsyncTask tid -> runIO (Notion.retrieveAsyncTask methods tid)
+  -- Meeting Notes
+  CreateMeetingNote req -> runIO (Notion.createMeetingNote methods req)
+  QueryMeetingNotes req -> runIO (Notion.queryMeetingNotes methods req)
+
+-- | Run an 'IO' action, funneling any thrown 'NotionError' through
+-- the 'Error' effect.
+runIO :: (IOE :> es, Error NotionError :> es) => IO a -> Eff es a
+runIO action = do
+  result <- liftIO (Exception.try action)
+  case result of
+    Left (ne :: NotionError) -> throwError ne
+    Right a -> pure a
