Automating Backups with Notion's API
Tags: python, notion, tutorial, backup
Published: 2021-06-18 11:32:11

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](https://www.notion.so/) 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](You can also use Notion Backups to easily back up your Notion data to a cloud storage provider of your choice. That way, you don't have to worry about your Notion data being lost.)
* Using a GitLab/Github CI with Notion's private API (the private API is subject to change at any time without notice)

Now that [Notion's API](https://developers.notion.com/) is available, there's a more robust way of backing up your data.

## Step 1: Create an internal integration

Head over to [My integrations](https://www.notion.so/my-integrations) page to create an internal integration. [Notion's docs](https://developers.notion.com/docs#internal-notion-integrations) 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 pages or databases. You have to manually add it to pages and databases you want to back up by using the invite function in the Share menu. Your integration won't be visible unless you click on the invite field.

[Notion's docs](https://developers.notion.com/docs#step-2-share-a-database-with-your-integration) 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. The Notion API exposes a [search](https://developers.notion.com/reference/post-search) endpoint that returns the top-level pages and databases your integration has access to.

```python
import requests

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

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

Keep in mind that the [search](https://developers.notion.com/reference/post-search) endpoint doesn't return child pages. To grab them, you'll have to query for pages using the [retrieve block children](https://developers.notion.com/reference/get-block-children) endpoint.

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

In large workspaces, results are more likely to be paginated. To obtain all items, you will have to recursively query the [search](https://developers.notion.com/reference/post-search) endpoint until the `has_more` parameter returns `false`.

## Step 4: Store your data

One way of storing your Notion workspace data is to write top-level pages and databases to separate JSON files and write child pages under a directory named after their parent files.

Here's how the final script looks like:

```python
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': '2022-06-28',
  '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))
```

## Step 5: Set up a cron job

Ideally, backups should be _set and forget_. Otherwise, you will probably 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. Then, create a cron entry:

```bash
crontab -e
```
Crontab has a specific syntax, and there's plenty of material [on the Internets](https://crontab.guru/) 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:

```bash
0 */6 * * * /usr/local/bin/python3 /full/path/to/notionbackup.py
```

This assumes that the Python script is called notionbackup.py and the Python interpreter is located in `/usr/local/bin/python3`. If unsure, you can locate the full path to the Python 3 interpreter:

```bash
which python3
```

That's all, folks! You can find the full code [on Github](https://github.com/NotionBackups/notion-backup-python).

You can also use [Notion Backups](https://notionbackups.com/) to easily back up your Notion data to a cloud storage provider of your choice. That way, you don't have to worry about your Notion data being lost.