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/1Pristine.execute_request/3Pristine.SDK.OpenAPI.ClientPristine.stream/3Pristine.OAuth2
It does not treat broad Pristine.Core.* internals as
its SDK contract.
Auth ownership is split intentionally:
Pristine.OAuth2owns the generic OAuth runtime behaviorNotionSDK.OAuthowns 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.Clientowns 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 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.1"}
]
endThen 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, and docs work
-
use release Hex/GitHub sources when running
mix deps.get,mix hex.build, ormix hex.publishsomix.lockstays publishable -
otherwise use Hex
pristine ~> 0.2.1plus GitHubsubdir:dependencies forpristine_codegenandpristine_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"))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
- Getting Started: install, defaults, client creation, and first calls
- Client Configuration: client options, Foundation runtime integration, retry tuning, typed responses, and transport overrides
- Versioning: default Notion version, override rules, and how the committed generated surface is versioned
- Capabilities, Permissions, and Sharing: what must be enabled or shared before content, comment, and user calls succeed
- Pages, Blocks, and Search: read-oriented page, block, markdown, and search flows
- Content Creation and Mutation: create, move, update, and append content
- Data Sources and Databases: metadata, queries, templates, and the
2025-09-03split - File Uploads, Comments, and Users: namespace walkthroughs and smaller edge workflows
- File Uploads and Page Attachments: upload-complete-attach workflows for files, covers, icons, and comments
- OAuth and Auth Overrides: authorization URLs, token exchange, saved token files, and request-scoped auth
- Low-Level Requests: the user-facing custom-request escape hatch on
NotionSDK.Client.request/2 - Pagination, Helpers, and Guards: helper surface around paginated responses and Notion ids
- Errors, Retries, and Observability:
%NotionSDK.Error{}, retry groups, request ids, and telemetry - Regeneration and Parity Workflow: snapshot refresh, code generation, and the JS oracle contract
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: runsmoke,content,data,files,mutations,oauth,cookbook,all, oreverything- 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.
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/client5.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
positionwithafter_block,start, andend -
page create
positionwithafter_block,page_start, andpage_end in_trashfields on modern page, block, database, data source, and upload responsesmeeting_notesblock 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