NotionSDK

Hex.pmHexDocsGitHubLicense

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:

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

What this SDK is

NotionSDK is intentionally thin:

The client owns runtime concerns such as auth, retries, transport, 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:

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

Install

def deps do
  [
    {:notion_sdk, "~> 0.2.0"}
  ]
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:

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

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

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.

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:

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:

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:

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