How to use Python with Notion API

Everybody uses Notion. And why not? It is an awesome tool. But what if you want to also add it to some of your apps? It seems you can now do it by using the Notion API and I will show how you can integrate it using Python.

Notion setup

First, you need to create a page that contains a database in Notion. In this example, we are going to make one that contains 3 fields: name, genre, and completed.

In order to have access to this page inside Python, we need to create an integration for it. For that, you can go to Settings => Integrations => Develop your own integrations. After that, you specify a name for your integration and click submit. In the end, you will get to this screen:

As you can see in the photo, you now have a secret key. Copy that because we will need it in our Python program.

We need to do 2 more steps in Notion before starting to write some code:

  • We need to go on the notion page, click the Share button, press Invite, and then you can select the integration that you just created from a list:
  • With the page opened in your browser, you will need a database id. Let's say the link is the following one: https://www.notion.so/19f00145217c4437afb06cfdbb2ad994?v=51972570a71c4b8b9b1feeec01e87eb5. The database id is 19f00145217c4437afb06cfdbb2ad994.

Python Setup

First, we import the requests library that we are going to use to interact with the Notion API. Then, we store the secret and database_id from the notion setup.

import json
import requests

token = 'secret_from_notion_integration'
database_id = 'database_id_from_link'

Next, we will use the requests library to interact with the Notion API.

The first function we are going to create is getMovies.

def getMovies():
  url = f'https://api.notion.com/v1/databases/{database_id}/query'

  r = requests.post(url, headers={
    "Authorization": f"Bearer {token}",
    "Notion-Version": "2021-08-16"
  })

  result_dict = r.json()
  movie_list_result = result_dict['results']

  print(movie_list_result)

Now, if you add a movie in Notion, and you call this function, you will see a lot of data. In order to make it more readeble and use only the information we need, we will make a helper function:

def mapNotionResultToMovie(result):
  # you can print result here and check the format of the answer.
  movie_id = result['id']
  properties = result['properties']
  genre = properties['Genre']['rich_text'][0]['text']['content']
  name = properties['Name']['title'][0]['text']['content']
  completed = properties['Completed']['checkbox']

  return {
    'genre': genre,
    'name': name,
    'completed': completed,
    'movie_id': movie_id
  }

And call it inside getMovies. The function should contain the following code:

def getMovies():
  url = f'https://api.notion.com/v1/databases/{database_id}/query'

  r = requests.post(url, headers={
    "Authorization": f"Bearer {token}",
    "Notion-Version": "2021-08-16"
  })

  result_dict = r.json()
  movie_list_result = result_dict['results']

  movies = []

  for movie in movie_list_result:

    movie_dict = mapNotionResultToMovie(movie)
    movies.append(movie_dict)

  return movies

Example of usage:

movies = getMovies()
# json.dumps is used to pretty print a dictionary 
print('Movie list:', json.dumps(movies, indent=2))

Now, you can use this function to display your movies inside Python. Pretty cool, right 😁 ?

The next function we are going to implement is createMovie. For this one, we will need to construct a payload similar to the response from getMovies.

def createMovie(name, genre, completed=False):
  url = f'https://api.notion.com/v1/pages'

  payload = {
    "parent": {
      "database_id": database_id
    },
    "properties": {
      "Name": {
        "title": [
          {
            "text": {
              "content": name
            }
          }
        ]
      },
      "Genre": {
        "rich_text": [
          {
            "text": {
              "content": genre
            }
          }
        ]
      },
      "Completed": {
        "checkbox": completed
      }
    }}

  r = requests.post(url, headers={
    "Authorization": f"Bearer {token}",
    "Notion-Version": "2021-08-16",
    "Content-Type": "application/json"
  }, data=json.dumps(payload))

  movie = mapNotionResultToMovie(r.json())
  return movie

You can use it like this:

createdMovie = createMovie('Movie1', 'Genre1', False)

print('Created Movie:', json.dumps(createdMovie, indent=2))

If you check Notion, you will see that a new entry in the table was created.🎉🎉🎉

The update function is pretty similar to the create one. The major difference is that we need to also take into consideration a movieId. We create the payload in a similar way, but we also add the movieId in the URL.

def updateMovie(movieId, movie):
  url = f'https://api.notion.com/v1/pages/{movieId}'

  payload = {
    "properties": {
      "Name": {
        "title": [
          {
            "text": {
              "content": movie['name']
            }
          }
        ]
      },
      "Genre": {
        "rich_text": [
          {
            "text": {
              "content": movie['genre']
            }
          }
        ]
      },
      "Completed": {
        "checkbox": movie['completed']
      }
    }}

  r = requests.patch(url, headers={
    "Authorization": f"Bearer {token}",
    "Notion-Version": "2021-08-16",
    "Content-Type": "application/json"
  }, data=json.dumps(payload))

  movie = mapNotionResultToMovie(r.json())
  return movie

In order to use this function, you first need to call getMovies(). In that response, you can get a movieId (in the ex: fdd0fa87-4729-43e6-ae3f-823d48b382ee) and use it like this:

updatedMovie = updateMovie('fdd0fa87-4729-43e6-ae3f-823d48b382ee', {
  'name': 'UpdatedMovie1',
  'genre': 'UpdatedGenre1',
  'completed': True
})
print('Update movie', json.dumps(updatedMovie, indent=2))

The last function that we are going to create is deleteMovie. In Notion, pages are using a property called archived. If we set that to true, then the page will be deleted. So, this function will use the update endpoint in order to change the value of the 'archived' boolean.

def deleteMovie(movieId):
  url = f'https://api.notion.com/v1/pages/{movieId}'

  payload = {
    "archived": True
  }

  r = requests.patch(url, headers={
    "Authorization": f"Bearer {token}",
    "Notion-Version": "2021-08-16",
    "Content-Type": "application/json"
  }, data=json.dumps(payload))

Now, you can use it with a movieId and if you check the database in Notion, that specific row will be deleted.

deleteMovie('a19e538d-10cc-40ec-91bb-f7237c93e428')

It is easier to interact with Notion from JavaScript because they provide a client, and in Python, we don't have one, but that shouldn't stop you to use it😁.

19