{"id":13594862,"url":"https://github.com/ramnes/notion-sdk-py","last_synced_at":"2026-10-19T09:05:14.608Z","repository":{"id":41273061,"uuid":"367164562","full_name":"ramnes/notion-sdk-py","owner":"ramnes","description":"Notion API client SDK, rewritten in Python! (sync + async)","archived":false,"fork":false,"pushed_at":"2026-10-11T00:32:27.000Z","size":5432,"stargazers_count":2193,"open_issues_count":39,"forks_count":173,"subscribers_count":19,"default_branch":"main","last_synced_at":"2026-10-12T21:41:35.763Z","etag":null,"topics":["api-client","async","dataclasses","httpx","notion","notion-api","python","python-client"],"latest_commit_sha":null,"homepage":"https://ramnes.github.io/notion-sdk-py","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ramnes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"claude":null,"gemini":null,"cursor":null,"copilot":null,"dco":null,"cla":null,"disclosure":null},"funding":{"custom":["https://paypal.me/ramnes"]}},"created_at":"2021-05-13T20:17:52.000Z","updated_at":"2026-10-11T00:32:32.000Z","dependencies_parsed_at":"2026-09-25T02:18:14.095Z","dependency_job_id":null,"html_url":"https://github.com/ramnes/notion-sdk-py","commit_stats":{"total_commits":205,"total_committers":32,"mean_commits":6.40625,"dds":"0.30243902439024395","last_synced_commit":"9cb98b2e29c1370af865f1c268e3c3d933f61ef9"},"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/ramnes/notion-sdk-py","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramnes%2Fnotion-sdk-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramnes%2Fnotion-sdk-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramnes%2Fnotion-sdk-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramnes%2Fnotion-sdk-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ramnes","download_url":"https://codeload.github.com/ramnes/notion-sdk-py/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramnes%2Fnotion-sdk-py/sbom","scorecard":{"id":760831,"data":{"date":"2025-08-11","repo":{"name":"github.com/ramnes/notion-sdk-py","commit":"13d7ec425b3549c372f02f5a71d94ab519bcf6b2"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.6,"checks":[{"name":"Maintained","score":4,"reason":"2 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 4","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":2,"reason":"Found 8/30 approved changesets -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: SECURITY.md:1","Info: Found linked content: SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: SECURITY.md:1","Info: Found text in security policy: SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/docs.yml:1","Warn: no topLevel permission defined: .github/workflows/notion-changelog-monitor.yml:1","Warn: no topLevel permission defined: .github/workflows/quality.yml:1","Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/docs.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/ramnes/notion-sdk-py/docs.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/docs.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/ramnes/notion-sdk-py/docs.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/notion-changelog-monitor.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/ramnes/notion-sdk-py/notion-changelog-monitor.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/notion-changelog-monitor.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/ramnes/notion-sdk-py/notion-changelog-monitor.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/quality.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/ramnes/notion-sdk-py/quality.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/quality.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/ramnes/notion-sdk-py/quality.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/quality.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/ramnes/notion-sdk-py/quality.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/ramnes/notion-sdk-py/test.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/ramnes/notion-sdk-py/test.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/ramnes/notion-sdk-py/test.yml/main?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/docs.yml:16","Warn: pipCommand not pinned by hash: .github/workflows/notion-changelog-monitor.yml:24","Warn: pipCommand not pinned by hash: .github/workflows/notion-changelog-monitor.yml:25","Warn: pipCommand not pinned by hash: .github/workflows/test.yml:26","Info:   0 out of   8 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned","Info:   0 out of   4 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 10 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-22T23:26:33.131Z","repository_id":41273061,"created_at":"2025-08-22T23:26:33.131Z","updated_at":"2025-08-22T23:26:33.131Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":344872684,"owners_count":38352877,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-10-03T21:59:58.778Z","status":"online","status_checked_at":"2026-10-19T02:00:06.414Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-client","async","dataclasses","httpx","notion","notion-api","python","python-client"],"created_at":"2024-08-01T16:01:40.138Z","updated_at":"2026-10-19T09:05:14.600Z","avatar_url":"https://github.com/ramnes.png","language":"Python","funding_links":["https://paypal.me/ramnes"],"categories":["Python","HarmonyOS","Third-party Web APIs"],"sub_categories":["Windows Manager"],"readme":"\u003c!-- markdownlint-disable --\u003e\n![notion-sdk-py](https://socialify.git.ci/ramnes/notion-sdk-py/image?font=Bitter\u0026language=1\u0026logo=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2F4%2F45%2FNotion_app_logo.png\u0026owner=1\u0026pattern=Circuit%20Board\u0026theme=Light)\n\n\u003cdiv align=\"center\"\u003e\n  \u003cp\u003e\n    \u003ca href=\"https://pypi.org/project/notion-client\"\u003e\u003cimg src=\"https://img.shields.io/pypi/v/notion-client.svg\" alt=\"PyPI\"\u003e\u003c/a\u003e\n    \u003ca href=\"tox.ini\"\u003e\u003cimg src=\"https://img.shields.io/pypi/pyversions/notion-client\" alt=\"Supported Python Versions\"\u003e\u003c/a\u003e\n    \u003cbr/\u003e\n    \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/ramnes/notion-sdk-py\" alt=\"License\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/ambv/black\"\u003e\u003cimg src=\"https://img.shields.io/badge/code%20style-black-black\" alt=\"Code style\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://codecov.io/github/ramnes/notion-sdk-py\"\u003e\u003cimg src=\"https://codecov.io/gh/ramnes/notion-sdk-py/branch/main/graphs/badge.svg\" alt=\"Coverage\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://pypistats.org/packages/notion-client\"\u003e\u003cimg src=\"https://img.shields.io/pypi/dm/notion-client\" alt=\"Package downloads\"\u003e\u003c/a\u003e\n    \u003cbr/\u003e\n    \u003ca href=\"https://github.com/ramnes/notion-sdk-py/actions/workflows/quality.yml\"\u003e\u003cimg src=\"https://github.com/ramnes/notion-sdk-py/actions/workflows/quality.yml/badge.svg\" alt=\"Code Quality\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/ramnes/notion-sdk-py/actions/workflows/test.yml\"\u003e\u003cimg src=\"https://github.com/ramnes/notion-sdk-py/actions/workflows/test.yml/badge.svg\" alt=\"Tests\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/ramnes/notion-sdk-py/actions/workflows/docs.yml\"\u003e\u003cimg src=\"https://github.com/ramnes/notion-sdk-py/actions/workflows/docs.yml/badge.svg\" alt=\"Docs\"\u003e\u003c/a\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n\u003c!-- markdownlint-enable --\u003e\n\n**_notion-sdk-py_ is a simple and easy to use client library for the official\n[Notion API](https://developers.notion.com/).**\n\nIt is meant to be a Python version of the reference [JavaScript SDK](https://github.com/makenotion/notion-sdk-js),\nso usage should be very similar between both. 😊 (If not, please open an issue\nor PR!)\n\n\u003c!-- markdownlint-disable --\u003e\n## Installation\n\u003c!-- markdownlint-enable --\u003e\n```shell\npip install notion-client\n```\n\n## Usage\n\n\u003e Use Notion's [Getting Started Guide](https://developers.notion.com/docs/getting-started)\n\u003e to get set up to use Notion's API.\n\nImport and initialize a client using an **integration token** or an\nOAuth **access token**.\n\n```python\nimport os\nfrom notion_client import Client\n\nnotion = Client(auth=os.environ[\"NOTION_TOKEN\"])\n```\n\nIn an asyncio environment, use the asynchronous client instead:\n\n```python\nfrom notion_client import AsyncClient\n\nnotion = AsyncClient(auth=os.environ[\"NOTION_TOKEN\"])\n```\n\nMake a request to any Notion API endpoint.\n\n```python\nfrom pprint import pprint\n\nlist_users_response = notion.users.list()\npprint(list_users_response)\n```\n\n\u003e [!NOTE]\n\u003e See the complete list of endpoints in the [API reference](https://developers.notion.com/reference).\n\nor with the asynchronous client:\n\n```python\nlist_users_response = await notion.users.list()\npprint(list_users_response)\n```\n\nThis would output something like:\n\n```text\n{'results': [{'avatar_url': 'https://secure.notion-static.com/e6a352a8-8381-44d0-a1dc-9ed80e62b53d.jpg',\n              'id': 'd40e767c-d7af-4b18-a86d-55c61f1e39a4',\n              'name': 'Avocado Lovelace',\n              'object': 'user',\n              'person': {'email': 'avo@example.org'},\n              'type': 'person'},\n             ...]}\n```\n\nAll API endpoints are available in both the synchronous and asynchronous clients.\n\nEndpoint parameters are grouped into a single object. You don't need to remember\nwhich parameters go in the path, query, or body.\n\n```python\nmy_page = notion.data_sources.query(\n    **{\n        \"data_source_id\": \"897e5a76-ae52-4b48-9fdf-e71f5945d1af\",\n        \"filter\": {\n            \"property\": \"Landmark\",\n            \"rich_text\": {\n                \"contains\": \"Bridge\",\n            },\n        },\n    }\n)\n```\n\n### Handling errors\n\nIf the API returns an unsuccessful response, an `APIResponseError` will be raised.\n\nThe error contains properties from the response, and the most helpful is `code`.\nYou can compare `code` to the values in the `APIErrorCode` object to avoid\nmisspelling error codes.\n\n```python\nimport logging\nfrom notion_client import APIErrorCode, APIResponseError, Client\n\ntry:\n    notion = Client(auth=os.environ[\"NOTION_TOKEN\"])\n    my_page = notion.data_sources.query(\n        **{\n            \"data_source_id\": \"897e5a76-ae52-4b48-9fdf-e71f5945d1af\",\n            \"filter\": {\n                \"property\": \"Landmark\",\n                \"rich_text\": {\n                    \"contains\": \"Bridge\",\n                },\n            },\n        }\n    )\nexcept APIResponseError as error:\n    if error.code == APIErrorCode.ObjectNotFound:\n        #\n        # For example: handle by asking the user to select a different data source\n        #\n        ...\n    else:\n        # Other error handling code\n        print(error)\n```\n\n### Logging\n\nThe client emits useful information to a logger. By default, it only emits warnings\nand errors.\n\nIf you're debugging an application, and would like the client to log request \u0026 response\nbodies, set the `log_level` option to `logging.DEBUG`.\n\n```python\nimport logging\nfrom notion_client import Client\n\nnotion = Client(\n    auth=os.environ[\"NOTION_TOKEN\"],\n    log_level=logging.DEBUG,\n)\n```\n\nYou may also set a custom `logger` to emit logs to a destination other than `stdout`.\nHave a look at [Python's logging cookbook](https://docs.python.org/3/howto/logging-cookbook.html)\nif you want to create your own logger.\n\n### Client options\n\n`Client` and `AsyncClient` both support the following options on initialization.\nThese options are all keys in the single constructor parameter.\n\n\u003c!-- markdownlint-disable --\u003e\n| Option       | Default value               | Type              | Description                                                                                                                               |\n| ------------ | --------------------------  | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| `auth`       | `None`                      | `string`          | Bearer token for authentication. If left undefined, the `auth` parameter should be set on each request.                                   |\n| `log_level`  | `logging.WARNING`           | `int`             | Verbosity of logs the instance will produce. By default, logs are written to `stdout`.                                                    |\n| `timeout_ms` | `DEFAULT_TIMEOUT_MS`        | `int`             | Number of milliseconds to wait before emitting a `RequestTimeoutError`                                                                    |\n| `base_url`   | `DEFAULT_BASE_URL`          | `string`          | The root URL for sending API requests. This can be changed to test with a mock server.                                                    |\n| `logger`     | Log to console              | `logging.Logger`  | A custom logger.                                                                                                                          |\n| `retry`      | See [constants](#constants) | `RetryOptions`    | Configuration for automatic retries on rate limits (429), service overloads (529), and server errors (500, 503). See [Automatic retries](#automatic-retries) below. |\n\u003c!-- markdownlint-enable --\u003e\n\n### Automatic retries\n\nThe client automatically retries requests that fail due to rate limiting or\ntransient server errors. By default, it will retry up to 2 times using\nexponential back-off with jitter.\n\n**Retryable errors:**\n\n- `rate_limited` (HTTP 429) - Too many requests; retried for all HTTP methods\n- `service_overload` (HTTP 529) - Service overloaded; retried for all HTTP methods\n- `internal_server_error` (HTTP 500) - Server error; retried only for GET and DELETE\n- `service_unavailable` (HTTP 503) - Service temporarily unavailable;\n  retried only for GET and DELETE\n\nServer errors (500, 503) are only retried for idempotent HTTP methods\n(GET, DELETE) to avoid duplicate side effects. Rate limits (429) and\nservice overloads (529) are retried for all methods since the server\nexplicitly asks clients to retry.\n\n**Configuration:**\n\n```python\nfrom notion_client import Client, RetryOptions\n\nnotion = Client(\n    auth=\"secret_...\",\n    retry=RetryOptions(\n        max_retries=5,          # Maximum retry attempts (default: 2)\n        initial_retry_delay_ms=500,  # Initial delay in ms (default: 1000)\n        max_retry_delay_ms=60000,    # Maximum delay in ms (default: 60000)\n    ),\n)\n```\n\nTo disable automatic retries:\n\n```python\nnotion = Client(auth=\"secret_...\", retry=False)\n```\n\n### Constants\n\nThe SDK exports named constants for all default values used by the client, as well\nas useful Notion-specific values. You can import them directly:\n\n```python\nfrom notion_client import (\n    DEFAULT_BASE_URL,          # \"https://api.notion.com\"\n    DEFAULT_TIMEOUT_MS,        # 60_000\n    DEFAULT_MAX_RETRIES,       # 2\n    DEFAULT_INITIAL_RETRY_DELAY_MS,  # 1_000\n    DEFAULT_MAX_RETRY_DELAY_MS,      # 60_000\n    MIN_VIEW_COLUMN_WIDTH,     # 32\n)\n```\n\n`MIN_VIEW_COLUMN_WIDTH` is the minimum width (in pixels) that a table column can\nhave in the Notion UI. Set a property's `width` to this value when creating or\nupdating a view to make a column appear collapsed -- useful for checkbox or\nstatus-as-checkbox columns:\n\n```python\nawait notion.views.create(\n    database_id=database_id,\n    name=\"My view\",\n    type=\"table\",\n    configuration={\n        \"table\": {\n            \"properties\": [\n                {\n                    \"property_id\": checkbox_prop_id,\n                    \"visible\": True,\n                    \"width\": MIN_VIEW_COLUMN_WIDTH,\n                },\n            ],\n        },\n    },\n)\n```\n\n### Full API responses\n\nThe following functions can distinguish between full and partial API responses.\n\n\u003c!-- markdownlint-disable --\u003e\n| Function                      | Purpose                                                                                                                                                                        |\n| --------------------------    | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `is_full_page`                | Determine whether an object is a full [Page object](https://developers.notion.com/reference/page)                                                                              |\n| `is_full_block`               | Determine whether an object is a full [Block object](https://developers.notion.com/reference/block)                                                                            |\n| `is_full_data_source`         | Determine whether an object is a full [Data source object](https://developers.notion.com/reference/data-source)                                                                |\n| `is_full_page_or_data_source` | Determine whether an object is a full [Page object](https://developers.notion.com/reference/page) or [Data source object](https://developers.notion.com/reference/data-source) |\n| `is_full_user`                | Determine whether an object is a full [User object](https://developers.notion.com/reference/user)                                                                              |\n| `is_full_comment`             | Determine whether an object is a full [Comment object](https://developers.notion.com/reference/comment-object)                                                                 |\n\u003c!-- markdownlint-enable --\u003e\n\n```python\nfrom notion_client.helpers import is_full_page\n\nfull_or_partial_pages = notion.data_sources.query(\n    data_source_id=\"897e5a76-ae52-4b48-9fdf-e71f5945d1af\"\n)\n\nfor page in full_or_partial_pages[\"results\"]:\n    if not is_full_page_or_data_source(page):\n        continue\n    print(f\"Created at: {page['created_time']}\")\n```\n\n### Utility functions\n\nThis package also exports a few utility functions that are helpful for dealing\nwith any of the paginated APIs.\n\n#### `iterate_paginated_api(function, **kwargs)`\n\nThis utility turns any paginated API into a generator.\n\n**Parameters:**\n\n- `function`: Any function on the Notion client that represents a paginated API\n  (i.e. accepts `start_cursor`.) Example: `notion.blocks.children.list`.\n- `**kwargs`: Arguments that should be passed to the API on the first and\n  subsequent calls to the API, for example a `block_id`.\n\n**Returns:**\n\nA generator over results from the API.\n\n**Example:**\n\n```python\nfrom notion_client.helpers import iterate_paginated_api\n\nfor block in iterate_paginated_api(\n    notion.blocks.children.list, block_id=parent_block_id\n):\n    # Do something with block.\n    ...\n```\n\n#### `collect_paginated_api(function, **kwargs)`\n\nThis utility accepts the same arguments as `iterate_paginated_api`, but collects\nthe results into an in-memory array.\n\nBefore using this utility, check that the data you are dealing with is small\nenough to fit in memory.\n\n**Parameters:**\n\n- `function`: Any function on the Notion client that represents a paginated API\n  (i.e. accepts `start_cursor`.) Example: `notion.blocks.children.list`.\n- `**kwargs`: Arguments that should be passed to the API on the first and\n  subsequent calls to the API, for example a `block_id`.\n\n**Returns:**\n\nAn array with results from the API.\n\n**Example:**\n\n```python\nfrom notion_client.helpers import collect_paginated_api\n\nblocks = collect_paginated_api(\n    notion.blocks.children.list, block_id=parent_block_id\n)\n# Do something with blocks.\n```\n\nBoth utilities also have async versions: `async_iterate_paginated_api` and\n`async_collect_paginated_api`.\n\n### Custom requests\n\nTo make requests directly to a Notion API endpoint instead of using the\npre-built families of methods, call the `request()` method. For example:\n\n```python\nimport json\n\n# POST /v1/comments\nresponse = notion.request(\n    path=\"comments\",\n    method=\"post\",\n    body={\n        \"parent\": {\"page_id\": \"5c6a28216bb14a7eb6e1c50111515c3d\"},\n        \"rich_text\": [{\"text\": {\"content\": \"Hello, world!\"}}],\n    },\n    # No `query` params in this example, only `body`.\n)\n\nprint(json.dumps(response, indent=2))\n```\n\n\u003e [!TIP]\n\u003e Usually, making custom requests with `notion.request()` isn't necessary, but\n\u003e can be helpful in some cases, e.g. when upgrading your [Notion API version](https://developers.notion.com/reference/versioning)\n\u003e incrementally before upgrading your SDK version. For example, if there's a new\n\u003e or renamed endpoint in the new API version that isn't yet available to call\n\u003e via a dedicated method on `Client`.\n\u003e\n\u003e In the above example, the simpler approach is to use\n\u003e `notion.comments.create()`.\n\nAnother customization you can make is to pass your own `httpx.Client` or\n`httpx.AsyncClient` to the `Client` or `AsyncClient` constructor. This might be\nhelpful for some execution environments where the default HTTPX client isn't\nsuitable.\n\n### Verifying webhook signatures\n\nIf your integration receives [Notion webhook deliveries](https://developers.notion.com/reference/webhooks),\nuse `verify_webhook_signature` to confirm each request was sent by Notion and\nwas not tampered with in transit. Notion signs every delivery with HMAC-SHA256\nover the raw request body using the subscription's verification token, and\nplaces the result in the `X-Notion-Signature` header as `sha256=\u003chex\u003e`.\n\n```python\nimport json\nimport os\n\nfrom fastapi import FastAPI, HTTPException, Request\nfrom notion_client import verify_webhook_signature\n\napp = FastAPI()\n\n\n# The body must be read as the raw bytes that arrived over the wire —\n# JSON-parsed and re-serialized bodies will not verify, because\n# re-serialization changes whitespace and key order.\n@app.post(\"/notion-webhook\")\nasync def notion_webhook(request: Request):\n    body = await request.body()\n    if not verify_webhook_signature(\n        body=body,\n        signature=request.headers.get(\"X-Notion-Signature\"),\n        verification_token=os.environ[\"NOTION_WEBHOOK_VERIFICATION_TOKEN\"],\n    ):\n        raise HTTPException(status_code=401, detail=\"invalid signature\")\n\n    event = json.loads(body)\n    # ...handle the event\n    return \"ok\"\n```\n\nThe same helper handles the initial verification handshake that Notion sends\nwhen you first register a webhook URL — the handshake body\n(`{\"verification_token\": \"...\"}`) is signed with the same token, so a single\ncode path covers both cases.\n\n`sign_webhook_payload(body, verification_token)` produces the same\n`sha256=\u003chex\u003e` header value, which is useful for unit-testing your webhook\nhandler without standing up a real subscription.\n\n## Testing\n\nRun the tests with the `pytest` command. If you want to test against all Python\nversions, you can run `tox` instead.\n\nThe tests are using `pytest-vcr`'s cassettes for simulating requests to the\nNotion API. To create new tests or run them without cassettes, you need to set\nup the environment variables `NOTION_TOKEN` and `NOTION_TEST_PAGE_ID` (a page\nwhere your integration has all the capabilities enabled).\n\nThe code will use the page at `NOTION_TEST_PAGE_ID` to generate a temporary\nenvironment with the Notion objects to be tested, which will be deleted\nat the end of the session.\n\n## Requirements and compatibility\n\nThis package supports the following minimum versions:\n\n- Runtime: Python \u003e= 3.8\n- httpx \u003e= 0.23.0\n\nEarlier versions may still work, but we encourage people building new applications\nto upgrade to the current stable.\n\n## Getting help\n\nIf you want to submit a feature request for Notion's API, or are experiencing\nany issues with the API platform, please email `developers@makenotion.com`.\n\nIf you found a bug with the library, please [submit an issue](https://github.com/ramnes/notion-sdk-py/issues).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Framnes%2Fnotion-sdk-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Framnes%2Fnotion-sdk-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Framnes%2Fnotion-sdk-py/lists"}