Automating Backups with Notion API

Updated:

Backing up your data on a consistent schedule is like buying insurance: you think you don't need it until you need it. With Notion becoming a second brain and knowledge management for many folks and organizations alike, it's paramount to have consistent backups if things go south.

Up until this point, you had two options when it came to backing up your Notion data:

  • Export Notion data manually
  • Duct tape by using a GitLab/Github CI with private API (the private API is subject to change at any time without notice)

Now that Notion's API is in private beta, there's a more robust way of backing up your data.

Step 1: Create an internal integration

Head over to My integrations to create an internal integration. Notion's docs give a good overview of the process.

If you want to back up your personal workspace, choose the internal integration. Keep in mind that you have to be an admin to access the workspace data.

Once you're done, grab your Internal Integration Token. You will need it later to authenticate your API requests.

Step 2: Share pages you want to back up

By default, your integration doesn't have access to any page or a database. You have to manually add it to pages/databases you want to back up by using the invite function in the Share menu. Your integration won't be visible unless you click the invite field. Notion's docs cover this topic in more detail.

Step 3: Query data

Unless you intend to back up a specific page only, you'd want to grab all pages and databases. Notion API exposes a search endpoint that returns top-level pages and databases your integration has access to.

import requests

# replace YOUR_INTEGRATION_TOKEN with your own secret token
headers = {
  'Authorization': 'Bearer YOUR_INTEGRATION_TOKEN',
  'Notion-Version': '2021-08-16',
  'Content-Type': 'application/json',
}

response = requests.post(
  'https://api.notion.com/v1/search',
  headers=headers,
)

Keep in mind that search endpoint doesn't return child pages. To grab them, you'll have to recursively query for pages using the retrieve block children endpoint.

for block in response.json()['results']:
  child_blocks = requests.get(
    f'https://api.notion.com/v1/blocks/{block["id"]}/children',
    headers=headers,
  )

Step 4: Store your data

One way of storing your Notion workspace would be to write top-level pages and databases to a file and write child pages to a directory named after their parent files.

import requests
import datetime
import os
import json

timestamp = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
folder = 'notionbackup-' + timestamp

os.mkdir(folder)

for block in response.json()['results']:
  with open(f'{folder}/{block["id"]}.json', 'w') as file:
    file.write(json.dumps(block))

  child_blocks = requests.get(
    f'https://api.notion.com/v1/blocks/{block["id"]}/children',
    headers=headers,
  )
  if child_blocks.json()['results']:
    os.mkdir(folder + f'/{block["id"]}')

    for child in child_blocks.json()['results']:
      with open(f'{folder}/{block["id"]}/{child["id"]}.json', 'w') as file:
        file.write(json.dumps(child))

Step 5: Set up a cron job

Ideally, backups should be set and forget process. Otherwise, you tend to forget about them.

On UNIX-like systems (macOS, Linux, BSD), you can set up a cron job that will run a script at a specified interval.

Before setting up a cron job, save this Python script on your machine and make it executable. If your script is named notionbackup.py, for example, and assuming you're in the same directory as the script, run the following command from your terminal:

chmod 644 notionbackup.py

Next, open up a text editor to create a cron entry:

crontab -e

Crontab has a specific syntax, and there's a plenty of material on the Internets about it. Let's say you want to run this script once every 6 hours. In the editor, opened by the previous command, paste the following:

0 */6 * * * ./notionbackup.py

Here's the final script for the reference:

import requests
import os
import datetime
import json

timestamp = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
folder = 'notionbackup-' + timestamp

os.mkdir(folder)

# replace YOUR_INTEGRATION_TOKEN with your own secret token
headers = {
  'Authorization': 'Bearer YOUR_INTEGRATION_TOKEN',
  'Notion-Version': '2021-08-16',
  'Content-Type': 'application/json',
}

response = requests.post('https://api.notion.com/v1/search', headers=headers)

for block in response.json()['results']:
  with open(f'{folder}/{block["id"]}.json', 'w') as file:
    file.write(json.dumps(block))

  child_blocks = requests.get(
    f'https://api.notion.com/v1/blocks/{block["id"]}/children',
    headers=headers,
  )
  if child_blocks.json()['results']:
    os.mkdir(folder + f'/{block["id"]}')

    for child in child_blocks.json()['results']:
      with open(f'{folder}/{block["id"]}/{child["id"]}.json', 'w') as file:
        file.write(json.dumps(child))

You can also use Notion Backups to automatically back up your Notion data to a storage provider of your choice. That way, you don't have to worry about your Notion data being lost.

Back up your Notion workspaces today.

Get started