Library / SDK
ramnes/notion-sdk-py avatar
ramnes/notion-sdk-py

notion-sdk-py: the Python client that keeps pace with Notion's API

Notion API client SDK, rewritten in Python! (sync + async)

2,192 stars171 forksPythonMIT

At a glance

What is it?
notion-sdk-py is a Python client library for the official Notion API, offering both a synchronous and an asynchronous client with the same surface. It is MIT licensed, tracks the reference JavaScript SDK closely, and its recent releases show it absorbing Notion's endpoint churn along with retry behaviour that most clients leave to the caller.
Who is it for?
What makes notion-sdk-py worth choosing over assembling httpx calls by hand is the maintenance argument more than the ergonomics one.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 7, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A Python port that follows the reference SDK

The project's own framing is direct: it is meant to be a Python version of the reference JavaScript SDK, so usage should be very similar between both. That sentence is the most useful thing in the README, because it tells you where to look when something behaves unexpectedly. If the Python client and the JavaScript client disagree, the JavaScript one is closer to what Notion documents, and several changes in the release history reference following the JS implementation by name.

The library is published to PyPI under a different name than the repository. The repository is notion-sdk-py, the GitHub user is ramnes, the author is Guillaume Gelin, and the install command is `pip install notion-client`. The import is `notion_client`. None of that is confusing once you know it, but every one of those names differs, which is the sort of thing that sends people to the wrong package.

The dependency surface is deliberately narrow. setup.py declares httpx at 0.23.0 or newer, plus typing_extensions at 4.0.0 or newer only when running below Python 3.10. Python support runs from 3.8 up to but not including 4, and the classifiers list every version in that range, including 3.13 and 3.14. The package ships a py.typed marker, so type checkers will use the inline annotations, and the project is classified as Development Status 5, Production/Stable. Licence is MIT.

One endpoint surface, two clients

The library ships a synchronous `Client` and an asynchronous `AsyncClient`, and the README states plainly that all API endpoints are available in both. The difference is in how you await them, not in what you can reach. A synchronous session initializes from an integration token or an OAuth access token:

python
import os
from notion_client import Client

notion = Client(auth=os.environ["NOTION_TOKEN"])

In an asyncio codebase you swap the class and prefix the calls with await:

python
from notion_client import AsyncClient

notion = AsyncClient(auth=os.environ["NOTION_TOKEN"])

Both clients accept the same four constructor options, all passed as keys of a single parameter object. `auth` is the bearer token, defaulting to None so it can be set per request instead. `log_level` defaults to `logging.WARNING`, which means the library stays quiet unless you ask it to talk. `timeout_ms` controls how long a request waits before a `RequestTimeoutError` is raised, and `base_url` points at the root URL for API requests, which the README notes can be redirected to test against a mock server.

Because the two clients share a surface, porting code between sync and async is mostly a matter of adding awaits rather than rewriting calls. That matters more than it first appears, because Notion integrations are frequently embedded in web applications where the surrounding stack is already async.

Grouped parameters instead of path, query and body

The design decision that separates this client from a thin httpx wrapper is how endpoint parameters are passed. Rather than making you remember which arguments belong in the path, which in the query string, and which in the request body, the library takes a single object and sorts it out. The README's example queries a data source with a filter:

python
my_page = notion.data_sources.query(
    **{
        "data_source_id": "897e5a76-ae52-4b48-9fdf-e71f5945d1af",
        "filter": {
            "property": "Landmark",
            "rich_text": {
                "contains": "Bridge",
            },
        },
    }
)

The endpoints are grouped by resource with dotted access, so users, pages, data sources, blocks and the rest hang off the client as attributes. That grouping is also how the library tracks Notion's own API changes, and it explains a rename visible in the version history: version 3.0.0 removed `is_full_page_or_database` in favour of `is_full_page_or_data_source`, following Notion's shift from databases to data sources. If you are upgrading across that boundary, that helper rename is the first thing to grep for.

The same major release also added OAuth token, introspect and revoke endpoints, a move method on the pages endpoint, and a mypy compatibility fix for Python 3.14. The error namespace was refactored to follow the JS SDK, including path traversal validation in the error module. None of these are headline features, and together they are the maintenance work that keeps a client usable against a fast-moving upstream API.

Errors you can branch on, and logging when you need detail

An unsuccessful API response raises `APIResponseError`, and the README points at the `code` property as the one to branch on. Rather than comparing raw strings, you compare against the `APIErrorCode` object so a typo becomes a runtime name error instead of a silent mismatch. The documented pattern catches the error, compares the code, and handles the specific case separately from the rest:

python
from notion_client import APIErrorCode, APIResponseError, Client

try:
    notion = Client(auth=os.environ["NOTION_TOKEN"])
    my_page = notion.data_sources.query(
        **{
            "data_source_id": "897e5a76-ae52-4b48-9fdf-e71f5945d1af",
        }
    )
except APIResponseError as error:
    if error.code == APIErrorCode.ObjectNotFound:
        #
        # For example: handle by asking the user to select a different data source
        #
        ...

The enum grew over time. Version 2.7.0 added `additional_data` and `request_id` fields to the `APIResponseError` class itself, which means you can correlate a failure with Notion's request identifier when you need support. Version 3.1.0 added `gateway_timeout` to the enum. Helpers added in 3.1.0 include `is_full_view`, `is_http_response_error`, `DEFAULT_TIMEOUT_MS` and `MIN_VIEW_COLUMN_WIDTH`, and constants were extracted into their own module.

For debugging, the default log level of WARNING means the client is silent during normal operation. When you want request and response bodies, raise it:

python
import logging
from notion_client import Client

notion = Client(
    auth=os.environ["NOTION_TOKEN"],
    log_level=logging.DEBUG,
)

You can also pass a custom logger to send output somewhere other than stdout, which is what you want if your application already has a logging setup.

Retries in 3.1.0 and the examples directory

The most useful thing in version 3.1.0, published 2026-05-12, is automatic retries on rate limits and transient server errors, using exponential backoff and configurable through `RetryOptions` on the client. That is a feature most hand-rolled clients leave to the caller, and getting it wrong is a common source of flaky integrations, because Notion enforces rate limits per token and a busy workspace will hit them. Request handling in client.py was enhanced to follow notion-sdk-js, which is the pattern behind the change. The same release added `pages.retrieve_markdown()` and `pages.update_markdown()` endpoints.

The examples directory is the other thing worth browsing before writing code. It holds ten runnable projects: `first_project/` for a minimal start, `intro_to_notion_api/` added in 3.0.0 as an explicit onboarding example, `databases/` and `database_email_update/` for query patterns, `file_uploads/`, `generate_random_data/` for seeding content, `parse_text_from_any_block_type/`, `web_form_with_fastapi/` for embedding the API in a web app, and two GitHub integration examples named `notion_github_sync/` and `notion_task_github_pr_sync/`. The FastAPI example in particular is the closest thing to a worked answer for the async question, since a web framework is exactly the environment where `AsyncClient` earns its place.

The rest of the repository is standard discipline: a `tests/` directory, `tox.ini` for multi-version testing, `setup.cfg`, a `requirements/` directory, `.pre-commit-config.yaml`, `.editorconfig`, `.coveragerc`, and MkDocs documentation in `docs/` with `mkdocs.yml` at the root. GitHub reports a push on 2026-09-27, stars sit at roughly 2,200 with 172 forks and 30 open issues, and the repository is not archived.

Editorial conclusion

What makes notion-sdk-py worth choosing over assembling httpx calls by hand is the maintenance argument more than the ergonomics one. The project is explicitly a port of Notion's reference JavaScript SDK, so endpoint groups and parameter shapes track the official client, and version 3.1.0 shows what that buys: markdown page endpoints, OAuth token handling, and automatic retries with exponential backoff for rate limits and transient server errors, all landed in a library whose dependency list is still just httpx. The breaking changes in 3.0.0 are worth reading before you pin a version, because `is_full_page_or_database` was removed and `is_api_error_code` went private. Start with `pip install notion-client`, authenticate with a token or OAuth access token, and reach for `AsyncClient` in an asyncio codebase since the endpoint surface is identical between the two.

Frequently asked questions

Is Notion a Python library?

Notion itself is a workspace application, and this repository is the Python library for talking to it. The package is published as notion-client and imported as notion_client, installed with `pip install notion-client`. Notion's own reference SDK is written in JavaScript.

What is the Notion API used for?

It lets a program read and write Notion workspace content instead of a person editing pages by hand. This client exposes those endpoints through grouped namespaces such as users, pages, data sources and blocks, and the examples directory shows task syncing, file uploads, database queries and FastAPI web forms.

How do I install and set up notion-sdk-py?

Run `pip install notion-client`, then create a client with an integration token or an OAuth access token: `notion = Client(auth=os.environ["NOTION_TOKEN"])`. Use `AsyncClient` instead in an asyncio codebase. Notion's getting started guide covers creating the integration and sharing a page with it.

Does notion-sdk-py support async and retries?

Yes. A synchronous `Client` and an asynchronous `AsyncClient` share the same endpoint surface. Version 3.1.0 added automatic retries with exponential backoff for rate limits and transient server errors, configurable through RetryOptions.

Official sources

  1. License: MIT
  2. Project website
  3. ramnes/notion-sdk-py on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ramnes-notion-sdk-py.svg)](https://hysenlabs.com/projects/ramnes-notion-sdk-py)