Package: notion-api
notion-apiDisclaimer: This is anunofficialpackage and has no affiliation with Notion.soA wrapper for Notion's API, aiming to simplify the dynamic nature of interacting with Notion.README contains examples of the main functionality, including: creating Pages/Databases/Blocks, adding/removing/editing properties, retrieving property values, and database queries.Some more in-depth walkthroughs can be be found inexamples/.This package is not complete - new features will continue to be added, and current features may change.Links: Notion API UpdatesAPI ReferenceNotion API ChangelogNotion.so ReleasesIf you haven't already, you'll need to setup aNotion Integrationand share specific pages/databases with the integration to interact with the API.Information on integration types and setup can be foundhere.Installpip install -U notion-apiUsageimportdotenvimportnotion# client will check for env var 'NOTION_TOKEN'dotenv.load_dotenv()homepage=notion.Page('773b08ff38b44521b44b115827e850f2')parent_db=notion.Database(homepage.parent_id)# will also look for env var `TZ` to set the default timezone. If not found, will default to local timezone.Indexing a page will search for page property values, and indexing a Database will search for property objects.A full list can be retrieved for both using;retrieve()method for a Page, with the optionalfilter_propertiesparameter.retrieveattribute for a Database.homepage['dependencies']# {#     "id": "WYYq",#     "type": "relation",#     "relation": [#         {#             "id": "7bcbc8e6-e237-434b-bd0d-6b56e044200b"#         }#     ],#     "has_more": false# }parent_db['dependencies']# {#     "id": "WYYq",#     "name": "dependencies",#     "type": "relation",#     "relation": {#         "database_id": "f5984a7e-2257-4ab0-9d0a-23ea12324031",#         "type": "dual_property",#         "dual_property": {#             "synced_property_name": "blocked",#             "synced_property_id": "wx%7DQ"#         }#     }# }See usage of retrieving values from a page inexamples/retrieving-property-items.mdBelow is a brief example if we were wanting to get the page id from the above propertydependenciesinhomepage.fromnotionimportpropertyitemsrelated_id:list[str]=propertyitems.relation(homepage.dependencies)>>>["7bcbc8e6-e237-434b-bd0d-6b56e044200b"]Both Page's and Database's have setters for title/icon/cover.homepage.title="new page"homepage.cover="https://www.notion.so/images/page-cover/webb1.jpg"homepage.icon="https://www.notion.so/icons/alien-pixel_purple.svg"Creating Pages/Databases/BlocksCreate objects by passing an existing Page/Database instance to thecreateclassmethods.The current version of the Notion api does not allow pages/databases to be created to the parentworkspace.new_database=notion.Database.create(parent_instance=testpage,database_title="Example Database",name_column="page",# This is the column containing page names. Defaults to "Name".is_inline=True,# can also toggle inline with setters.description="Database description can go here.",)new_page=notion.Page.create(new_database,page_title="A new database row")Blocks are also created using classmethods. They require a parent instance of eitherPageorBlockto append the new block too.
The newly created block is returned as an instance ofBlock, which can be used as the parent instance to another nested block.By default, blocks are appended to the bottom of the parent block.To append the block somewhere else other than the bottom of the parent block, use theafterparameter and set its value to the ID of the block that the new block should be appended after. The block_id used in theafterparamater must still be a child to the parent instance.fromnotionimportpropertiesasprop# `original_synced_block` refers to the original synced block in the Notion UI.original_synced_block=notion.Block.original_synced_block(homepage)# Adding content to the synced blocknotion.Block.paragraph(original_synced_block,[prop.RichText("This is a synced block.")])# Referencing the synced block in a new page.notion.Block.duplicate_synced_block(new_page,original_synced_block.id)There are few extensions to theBlockclass that have specific functions unique to their block-type.Below is an example usingCodeBlock. The others areTableBlock,EquationBlock,RichTextBlock, andToDoBlock. You can see usage for them inexamples/block_extensions.md.code_block=notion.CodeBlock("84c5721d8a954667902a757f0033f9e0")git_graph=r"""%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { 'darkMode':'true', 'git0': '#ff0000', 'git1': '#00ff00', 'git2': '#0000ff', 'git3': '#ff00ff', 'git4': '#00ffff', 'git5': '#ffff00', 'git6': '#ff00ff', 'git7': '#00ffff' } } }%%gitGraphcommitbranch developcommit tag:"v1.0.0"commitcheckout maincommit type: HIGHLIGHTcommitmerge developcommitbranch featureAcommit"""code_block.language=prop.CodeBlockLang.mermaidcode_block.code=git_graphcode_block.caption="Example from https://mermaid.js.org/syntax/gitgraph.html"Example Function: Usingnotion.Workspace()to retrieve a user, and appending blocks in a page to mention user/date.definline_mention(page:notion.Page,message:str,user_name:str)->None:mentionblock=notion.Block.paragraph(page,[prop.Mention.user(notion.Workspace().retrieve_user(user_name=user_name),annotations=prop.Annotations(code=True,bold=True,color=prop.BlockColor.purple),),prop.RichText(" - "),prop.Mention.date(datetime.now().astimezone(page.tz).isoformat(),annotations=prop.Annotations(code=True,bold=True,italic=True,underline=True,color=prop.BlockColor.gray,),),prop.RichText(":"),])# First method returned the newly created block that we append to here:notion.Block.paragraph(mentionblock,[prop.RichText(message)])notion.Block.divider(page)>>>homepage=notion.Page("0b9eccfa890e4c3390175ee10c664a35")>>>inline_mention(page=homepage,message="example",user_name="AYVI")Add, Set, & Delete: Page property values | Database property objectsThe first argument for all database property methods is the name of the property,If a property of that name does not exist, then a new property will be created.
If a property of that name already exists, but it's a different type than the method used - then the API will overwrite this and change the property object to the new type.The original parameters will be saved if you decide to switch back (i.e. if you change a formula column to a select column, upon changing it back to a formula column, the original formula expression will still be there).new_database.formula_column("page_id",expression="id()")new_database.delete_property("url")new_database.multiselect_column("New Options Column",options=[prop.Option("Option A",prop.PropertyColor.red),prop.Option("Option B",prop.PropertyColor.green),prop.Option("Option C",prop.PropertyColor.blue),],)# if an option does not already exist, a new one will be created with a random color.# this is not true for `status` column types, which can only be edited via UI.new_page.set_multiselect("options",["option-a","option-b"])Database QueriesA singlenotion.query.PropertyFilteris equivalent to filtering one property type in Notion.
To build nested filters, usenotion.query.CompoundFilterand group property filters chained together by_and(...)/_or(...).The database methodquery()will return the raw response from the API.The methodquery_pages()will extract the page ID for each object in the array of results, and return a list ofnotion.Pageobjects.note: in v0.6.0, new query methods were added:query_all()&query_all_pages()- these handle pagination, and instead of providing thenext_cursorandhas_morekeys,
they will iterate through all the results, or up to themax_page_sizeparamater. These 2 methods will replace the original ones in a later update.fromdatetimeimportdatetimefromdatetimeimporttimedeltafromnotionimportqueryTODAY=datetime.combine(datetime.today(),datetime.min.time())TOMORROW=TODAY+timedelta(1)query_filter=query.CompoundFilter()._and(query.PropertyFilter.date("date","created_time","on_or_after",TODAY.isoformat()),query.PropertyFilter.date("date","created_time","before",TOMORROW.isoformat()),query.CompoundFilter()._or(query.PropertyFilter.text("name","title","contains","your page title"),query.PropertyFilter.text("name","title","contains","your other page title")),)query_sort=query.SortFilter([query.PropertyValueSort.ascending("your property name"),query.EntryTimestampSort.created_time_descending(),])query_result=new_database.query(filter=query_filter,sort=query_sort,page_size=5,filter_property_values=["name","options"],)Exceptions & Validating ResponsesErrors in Notion requests return an object with the keys: 'object', 'status', 'code', and 'message'.
Exceptions are raised by matching the error code and returning the message. For example:homepage._patch_properties(payload={'an_incorrect_key':'value'})# Example error object for line above..# {#   'object': 'error',#   'status': 400,#   'code': 'validation_error',#   'message': 'body failed validation: body.an_incorrect_key should be not present, instead was `"value"`.'# }Traceback(mostrecentcalllast):
File"c:\path\to\file\_.py",line6,in<module>homepage._patch_properties(payload={'an_incorrect_key':'value'})File"c:\...\notion\exceptions\validate.py",line48,invalidate_responseraiseNotionValidationError(message)notion.exceptions.errors.NotionValidationError:bodyfailedvalidation:body.an_incorrect_keyshouldbenotpresent,insteadwas`"value"`.
Error400:Therequestbodydoesnotmatchtheschemafortheexpectedparameters.Possible errors are:NotionConflictErrorNotionDatabaseConnectionUnavailableNotionGatewayTimeoutNotionInvalidGrantNotionInternalServerErrorNotionInvalidJsonNotionInvalidRequestNotionInvalidRequestUrlNotionMissingVersionNotionObjectNotFoundNotionRateLimitedNotionRestrictedResourceNotionServiceUnavailableNotionUnauthorizedNotionValidationErrorA common error to look out for isNotionObjectNotFound. This error is often raised because your bot has not been added as a connection to the page.By default, a bot will have access to the children of any Parent object it has access too. Be sure to double check this connection when moving pages.If you're working on a page that your token has access to via its parent page/database, but you never explicitly granted access to the child page -  and you later move that child page out, then it will lose access.