Skip to content

nshkrdotcom/notion_sdk

Repository files navigation

NotionSDK

Hex.pm HexDocs GitHub License

NotionSDK

Elixir SDK for the Notion API, generated from committed upstream Notion reference fixtures and executed through the shared pristine runtime.

That pristine dependency is intentional. notion_sdk targets the bounded public runtime surface:

  • Pristine.foundation_context/1
  • Pristine.execute_request/3
  • Pristine.SDK.OpenAPI.Client
  • Pristine.stream/3
  • Pristine.OAuth2

It does not treat broad Pristine.Core.* internals as its SDK contract.

Auth ownership is split intentionally:

  • Pristine.OAuth2 owns the generic OAuth runtime behavior
  • NotionSDK.OAuth owns Notion-specific helper semantics and CLI UX
  • durable install and secret authority stay outside the SDK

What this SDK is

NotionSDK is intentionally thin:

  • generated endpoint modules stay close to upstream JSON payloads
  • NotionSDK.Client owns Notion-specific runtime defaults and auth behavior
  • generic transport, retry, telemetry, and path-safety behavior comes from pristine
  • hand-written guides explain the supported runtime contract and common workflows around the generated API reference

The client selects Notion-specific defaults for auth, retry groups, transport options, and headers. Generated modules now emit request maps with stable runtime metadata and pass them through the shared Pristine.execute_request/3 boundary. Workspace resource ids stay on each request:

That boundary is intentional. notion_sdk keeps its public SDK surface in NotionSDK.*, while the lower unary HTTP lane stays inside pristine and its Execution Plane-backed transport substrate instead of becoming a repo-local public path here.

{:ok, page} =
  NotionSDK.Pages.retrieve(client, %{
    "page_id" => "00000000-0000-0000-0000-000000000000"
  })

Install

def deps do
  [
    {:notion_sdk, "~> 0.2.1"}
  ]
end

Then fetch dependencies:

mix deps.get

For active local development beside sibling checkouts, notion_sdk can also be consumed from a relative path:

{:notion_sdk, path: "../notion_sdk"}

Within this repo, the shared pristine dependencies now resolve by one stable policy:

  • prefer sibling-relative paths when local checkouts exist for normal compile, test, docs, and mix deps.get
  • use the published dependency surface when running mix hex.build or mix hex.publish
  • set NOTION_SDK_HEX_DEPS=1 if you want mix deps.get to ignore sibling ../pristine checkouts and resolve the published dependency surface instead
  • otherwise use Hex pristine ~> 0.2.1 plus GitHub subdir: dependencies for pristine_codegen and pristine_provider_testkit

That removes the need for a committed vendored deps/ tree while keeping local development and downstream dependency behavior aligned.

Make one request

Create a client with a bearer token:

client = NotionSDK.Client.new(auth: System.fetch_env!("NOTION_TOKEN"))

This env-backed constructor is standalone SDK compatibility. In governed runtime flows, env vars, app config defaults, OAuth saved token files, request auth overrides, and workspace ids from OAuth responses cannot satisfy authority. Pass a NotionSDK.GovernedAuthority value instead:

authority =
  NotionSDK.GovernedAuthority.new!(
    base_url: "https://api.notion.com",
    credential_ref: "credential-handle",
    credential_lease_ref: "lease-handle",
    target_ref: "notion-target",
    workspace_ref: "notion-workspace",
    headers: %{"X-Governed-Target" => "notion-target"},
    credential_headers: %{"Authorization" => "Bearer materialized-token"}
  )

client = NotionSDK.Client.new(governed_authority: authority)

Fetch the bot user tied to that token:

{:ok, me} = NotionSDK.Users.get_self(client)

Search the workspace:

{:ok, result} =
  NotionSDK.Search.search(client, %{
    "query" => "Roadmap",
    "page_size" => 10
  })

Responses stay as JSON-shaped maps by default. Opt in to typed request/response validation and generated structs only when you want them:

typed_client =
  NotionSDK.Client.new(
    auth: System.fetch_env!("NOTION_TOKEN"),
    typed_responses: true
  )

Docs map

Examples map

  • Live Examples README: the real-service regression-proof suite, fixture requirements, mutation notes, and grouped runner commands
  • Cookbook Examples README: task-oriented workflows that layer multiple endpoints into one runnable flow
  • examples/run_all.sh: run smoke, content, data, files, mutations, oauth, cookbook, all, or everything
  • Generated module docs on HexDocs: the source of truth for exact request/response shapes on each endpoint wrapper

The live examples use NOTION_EXAMPLE_* environment variables for fixture ids and URLs. The SDK itself does not read those values unless an example passes them into a request.

For custom requests that are not covered by a generated wrapper yet, use the simplified raw request shape documented in Low-Level Requests. That escape hatch still runs through the shared pristine request pipeline and path-safety checks.

OAuth onboarding

Most public integrations already have a registered HTTPS redirect URI in Notion. That is the easiest onboarding path:

export NOTION_OAUTH_CLIENT_ID="..."
export NOTION_OAUTH_CLIENT_SECRET="..."
export NOTION_OAUTH_REDIRECT_URI="https://your-app.example.com/notion/callback"

mix notion.oauth --save --manual --no-browser

That flow prints the authorization URL, waits for approval in the browser, then exchanges the temporary code and saves the token JSON to ~/.config/notion_sdk/oauth/notion.json by default.

Saved token persistence and refresh merge behavior now come from the upstream Pristine.OAuth2.SavedToken workflow, while mix notion.oauth stays the thin Notion-specific wrapper around env vars, CLI wording, and default paths. Those env vars and saved files are standalone onboarding and local persistence only; governed clients reject them and require NotionSDK.GovernedAuthority.

For persisted bearer auth, point the client at the generic file token source:

client =
  NotionSDK.Client.new(
    oauth2: [
      token_source:
        {Pristine.Adapters.TokenSource.File,
         path: NotionSDK.OAuthTokenFile.default_path()}
    ]
  )

Use the full walkthrough in OAuth and Auth Overrides for loopback redirects, programmatic authorization URLs, refresh flows, and explicit Basic auth overrides on OAuth control endpoints.

API versioning

The public default remains:

  • Notion API version header: 2025-09-03
  • JS SDK oracle: @notionhq/client 5.12.0
  • Bounded parity inventory: priv/upstream/parity_inventory.json

You can override the version header per client:

client =
  NotionSDK.Client.new(
    auth: System.fetch_env!("NOTION_TOKEN"),
    notion_version: "2025-09-03"
  )

notion_sdk does not automatically move its default header forward. The supported default in this repo stays 2025-09-03 until the committed fixtures, generated code, and tests move together.

The committed generated surface currently includes fields and request shapes such as:

  • block append position with after_block, start, and end
  • page create position with after_block, page_start, and page_end
  • in_trash fields on modern page, block, database, data source, and upload responses
  • meeting_notes block response support in the generated block unions

If you override notion_version, keep that override explicit in code and test the affected flows in your workspace. Use Versioning for the current support contract.

Parity and regeneration

Surface proved in this package today:

  • 35 documented endpoint definitions in the committed bounded parity inventory
  • request building for OAuth, markdown, multipart uploads, and custom headers
  • helper behavior, retry behavior, and error mapping

Supported maintenance commands:

mix notion.generate
mix notion.refresh
mix notion.refresh --snapshots-only

The maintainer tasks accept explicit path overrides such as --reference-root, --notion-docs-root, and --js-sdk-root, so sibling checkouts are optional rather than required.

Use Regeneration and Parity Workflow for the artifact map, refresh steps, and oracle details.

Tests and maintenance

Recommended verification loop:

mix compile --warnings-as-errors
mix test
mix dialyzer
mix credo --strict
mix docs

About

Native Elixir SDK for the Notion API — comprehensive, idiomatic client for Notion workspaces, databases, pages, blocks, users, comments, and search. Built on OTP with supervised HTTP, automatic rate limiting, pagination helpers, and robust error handling for BEAM applications.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors