Explore API

Tags: #notion #productivity #operations #snippet
Author: Jeremy Ravenel
Description: This notebook provides an exploration of the Notion API, allowing users to access and manipulate data from Notion.


import requests
import pandas as pd
import json

Setting up Notion connections

  1. 1.
    Create a page or a database on Notion.
  2. 2.
    Create an integration in your workspace.
  3. 3.
    Share your page or database to this integration.
More explanation on how to do this here:
DATABASE_ID_TEST = "a296bd16b7284bc494aa91f50ad64d30" #
PAGE_ID = "e2e8b31737174dbe86b9ae65f9b8eb9c" # click on Page and Get ID :
INTEGRATION_TOKEN = "secret_gF6bJPSyOgt5oZgb2sgT1yiMxfS4LqNmWmd2M8S5vzl"


Get database properties

More information here:
response = requests.get(database_url, headers={"Authorization": f"{INTEGRATION_TOKEN}"})
More information here:
database_url = NOTION_DB_URL + DATABASE_ID_TEST + "/query"
query = {"filter": {"property": "High Priority", "checkbox": {"equals": True}}}
query = {
"filter": {
"property": "Cost of next trip",
"number": {"greater_than_or_equal_to": 0.5},
headers = {"Authorization": f"{INTEGRATION_TOKEN}", "Notion-Version": "2021-05-13"}
response =, headers=headers, data=query)
df_structure = pd.DataFrame.from_dict(response.json()["results"])
print("The size of the df is", df_structure.shape)
⚠️ Notion's API allows us to retrieve a maximum of 100 records. So if your base is bigger than 100 records it will only retrieve the 100 last edited ones.
⚠️ If your database has some relation to some other databases, think to share the linked databases with the integration aswell.
As we can see the content of the Notion table is in the properties column. We will now extract it and see what it contains.
The column properties contain a dictionary for each Notion record. We will exctract each of these disctionnaries and create a new dataFrame.
list_dict = []
for index, row in df_structure.iterrows():
temp_df = pd.DataFrame.from_dict(list_dict)
# Get the columns name in a list to use them later
columns = temp_df.columns.values.tolist()
As we can see, each of the properties contains another dict of the information.
Let's see how the dictionnary containing the data is structured.
for index, value in temp_df.iloc[2].items():
Let's create a small function to extract the data.
All the properties contain an id and a type. The type will then be used to find the original information of the property.
Sometimes, the data will be contained directly as a string, sometimes it will be a dict sometimes it will be a list of dict.

Query database

def extract_name_or_plaintext(dictionnary):
# Given a dictionnary it will output the string of the key name or plain_text
if "name" in dictionnary.keys():
return dictionnary["name"]
elif "plain_text" in dictionnary.keys():
return dictionnary["plain_text"]
return ""
def extract_date(dictionnary):
# For the moment we extract only the starting date of a date field
# Example {'id': 'prop_1', 'type': 'date', 'date': {'start': '2018-03-21', 'end': None}}
# Input : {'start': '2018-03-21', 'end': None}
return dictionnary["start"]
def extract_data(element):
# input: a dictionnary of a notion property
# Exemple: {'id': 'W#4k', 'type': 'select', 'select': {'id': 'b305bd26-****-****-****-c78e2034db8f', 'name': 'Client', 'color': 'green'}}
# output: the string containing the information of the dict. (Client in the exemple)
if type(element) is dict:
dict_type = element["type"]
informations = element[dict_type]
if dict_type == "date":
informations = extract_date(informations)
elif type(informations) is dict:
informations = extract_name_or_plaintext(informations)
elif type(informations) is list:
informations_temp = ""
for element_of_informations_list in informations:
informations_temp += (
extract_name_or_plaintext(element_of_informations_list) + ", "
informations = informations_temp[:-2]
return informations
return ""
all_list = []
for i in range(temp_df.shape[0]):
temp_list = []
for index, value in temp_df.iloc[i].items():
df_content = pd.DataFrame.from_records(all_list, columns=columns)

Get only visible headers


Get full headers

df_full = pd.concat([df_structure, df_content], axis=1)

Get page properties

There is two different API calls to interact with a page.
  • Get a page will give us a page properties:
  • Get block children will give us the page content:
response = requests.get(
headers={"Authorization": f"{INTEGRATION_TOKEN}", "Notion-Version": "2021-05-13"},

Retrieve a page content

page_url = NOTION_PAGE_CONTENT + PAGE_ID + "/children"
headers = {"Authorization": f"{INTEGRATION_TOKEN}", "Notion-Version": "2021-05-13"}
response = requests.get(page_url, headers=headers)
Some types are accessible. But some other types are unsupported and cannot be read through the API.
Some unsupported types :
  • image
  • bookmarked link
  • other page (it has an unsupported type but it will be readable through its page id)

Create a record

page_url = NOTION_PAGE_URL
# Surprisingly in this case you need to add "Content-Type": "application/json"
# If not you will an error 400: body failed validation: body.parent should be defined, instead was 'undefined'.
headers = {
"Authorization": f"{INTEGRATION_TOKEN}",
"Notion-Version": "2021-05-13",
"Content-Type": "application/json",
name = {"title": [{"text": {"content": "Added via API NAAS"}}]}
company = {"rich_text": [{"text": {"content": "Test Company"}}]}
status = {"select": {"name": "Lost"}}
est_value = {"number": 10000}
header = {
"object": "block",
"type": "heading_2",
"heading_2": {"text": [{"type": "text", "text": {"content": "Naas API test"}}]},
paragraph = {
"object": "block",
"type": "paragraph",
"paragraph": {
"text": [
"type": "text",
"text": {
"content": "Notebooks as a service for data geeks. Naas notebooks enable you to script faster with low-code formulas & templates. Automate all your tasks in minutes..",
to_do = {
"object": "block",
"type": "to_do",
"to_do": {
"text": [
"type": "text",
"text": {
"content": "Automate all your tasks in minutes..",
"type": "text",
"text": {
"content": "Script faster",


Setup object to post

myobj = {
"parent": {"database_id": page_id},
"properties": {
"Name": name,
"Company": company,
"Status": status,
"Estimated Value": est_value,
"children": [header, paragraph, to_do],

Post record

data = json.dumps(myobj)
response =, headers=headers, data=data)
if "status" in response.json().keys():
if response.json()["status"] != 200:
print("Error:", response.json()["message"])
elif "object" in response.json().keys():
print("✅ Your data was added to Notion")