更新日: ・ 自動化ノートの管理人

Pythonの標準ライブラリだけでNotion APIに1行書き込む

「処理が終わったら、Notionのデータベースに1行だけ記録したい」。それだけのために、仮想環境(venv)を作って pip install requests をするのは、少し大げさです。しかも自動実行のスクリプトでは、ライブラリが入っていないPCで動かなくなる、という失敗の種にもなります。

私は、自動実行が失敗したときにNotionへ確認用のタスクを1件書き残すスクリプトを、標準ライブラリの urllib だけで作りました。この記事では、その作り方を、家の用事の例(買い物メモ)に置き換えてまとめます。

なぜ標準ライブラリにしたか

Pythonの公式ドキュメントでは、より高機能なHTTPクライアントとして requests パッケージが勧められています。ただ、Notion APIに送るのは「JSONを1回POSTする」だけです。urllib.request で足りるので、私は次の理由で標準ライブラリを選びました。

書き込みまでの流れ

  1. 1. インテグレーション作ってトークンを受け取る
  2. 2. 共有書き込み先に「コネクトを追加」する
  3. 3. トークンを置く環境変数か.envに。コードには書かない
  4. 4. POSTurllibでページを作る
コードを書く前の1〜3が、つまずきの大半を占めます。

トークンを用意する

Notionの公式ドキュメントでは、内部インテグレーションの作り方はこう説明されています。開発者ポータルで新しいコネクションを作り、「Configuration」タブでAPIトークンを取得します(ワークスペースのオーナーである必要があります)。なお、公式には、自分の権限で動く「個人アクセストークン」も別に用意されています。

次に、書き込み先のページかデータベースを開き、右上のメニューから「Add connections(コネクトを追加)」で作ったコネクションを選びます。公式ドキュメントでは、この共有をしないとコネクションはページを操作できないと説明されています。

私が一度はまったのは、ここです。すでに動いているトークンを別の用途に使い回したところ、新しく書き込み先にしたページには共有が済んでおらず、404で静かに失敗し続けました。詳しくはNotion APIが「object_not_found(404)」を返すときの確認手順にまとめています。

トークンの扱いについて、公式ドキュメントは「ソースコードやバージョン管理に入れず、環境変数から読む」ように勧めています。私は、スクリプトと同じフォルダの .env に NOTION_TOKEN=… と1行だけ書き、スクリプトがそれを読む形にしています。なお、Pythonの os.environ は .env を自動では読みません。下のコードでは、環境変数に無ければ .env を数行で読むようにしています。同期フォルダやGitに入れる場合は、.env を除外するのを忘れないでください。

ページを1つ作る最小のコード

ページを作るエンドポイントは POST /v1/pages です。リクエストには、Authorization: Bearer のトークンと、Notion-Version ヘッダーが要ります。公式ドキュメントでは、Notion-Version はすべてのリクエストに必須で、2026年10月時点の最新は 2026-03-11 と書かれています。

次の例は、「名前」「日付」という2つのプロパティを持つデータベースに、買い物メモを1行足します(プロパティ名は例です。自分のデータベースの名前に合わせます)。

import json
import os
import urllib.error
import urllib.request
from pathlib import Path

NOTION_VERSION = "2026-03-11"
DATA_SOURCE_ID = "ここにデータソースのID"


def load_token():
    """環境変数 NOTION_TOKEN か、スクリプトと同じフォルダの .env から読む"""
    token = os.environ.get("NOTION_TOKEN")
    if token:
        return token
    env_file = Path(__file__).with_name(".env")
    if env_file.exists():
        for line in env_file.read_text(encoding="utf-8").splitlines():
            if line.startswith("NOTION_TOKEN="):
                return line.split("=", 1)[1].strip()
    raise SystemExit("NOTION_TOKEN が見つかりません")


TOKEN = load_token()

payload = {
    "parent": {"type": "data_source_id", "data_source_id": DATA_SOURCE_ID},
    "properties": {
        "名前": {"title": [{"type": "text", "text": {"content": "牛乳を買う"}}]},
        "日付": {"date": {"start": "2026-10-20"}},
    },
}

req = urllib.request.Request(
    "https://api.notion.com/v1/pages",
    data=json.dumps(payload).encode("utf-8"),
    method="POST",
    headers={
        "Authorization": f"Bearer {TOKEN}",
        "Notion-Version": NOTION_VERSION,
        "Content-Type": "application/json",
    },
)

try:
    with urllib.request.urlopen(req, timeout=15) as resp:
        print("作成できました:", resp.status)
except urllib.error.HTTPError as e:
    print("失敗: HTTP", e.code, e.read().decode("utf-8", errors="replace"))
    raise SystemExit(1)
except OSError as e:
    print("通信できませんでした:", e)
    raise SystemExit(1)

ポイントは3つです。

データベースに行を足すときの「親」の指定

Notionの公式のアップグレードガイドでは、APIバージョン 2025-09-03 からデータソースという考え方が入り、データベースに行を足すときの親は data_source_id に切り替えるよう案内されています。ページ作成のリファレンスには親の型として database_id もまだ載っていますが、ガイドの案内に沿って data_source_id を使うのが確実です。旧バージョン(2022-06-28)では database_id で指定する形でした。

Notion-Versionデータベースに行を足すときの親
2022-06-28{"database_id": "…"}
2025-09-03 以降{"data_source_id": "…"}(公式ガイドの案内)

データソースのIDは、データベースの取得APIが返す data_sources の一覧から、またはNotionの画面のデータベースの設定にある「Manage data sources」の「Copy data source ID」で取れます。URLから取れるIDはデータベースのIDなので、混同しないでください。URLの読み分けにはNotionのID取り出しツールが使えます。

通常のページの子としてページを作るなら、親は page_id で、プロパティに指定できるのは title だけです(公式のリファレンスの説明)。

エラーの中身を読む

失敗したときは、HTTPError が起きます。Pythonの公式ドキュメントでは、この例外はレスポンスとしても使え、read() で本文を読めると説明されています。Notionの公式ドキュメントでは、エラーの本文には code と message が入ります。HTTPのステータス(404など)は e.code で取れます。

except urllib.error.HTTPError as e:
    body = json.loads(e.read().decode("utf-8", errors="replace"))
    print(e.code, body.get("code"), body.get("message"))
状態code公式の説明
400validation_errorリクエストの本文が想定の形と合わない。messageを見る
401unauthorizedトークンが正しくない
404object_not_foundそのトークンからは存在しない。共有されていない場合もある
429rate_limitedリクエストが多すぎる。間を空けて再試行する

上の例では失敗したら終了コード1で止めていますが、私の実際のスクリプトは、自動実行の失敗を知らせるための「おまけ」の処理なので、書き込みに失敗しても止めず、ステータスと本文を標準エラー(sys.stderr)に出すだけにしています。ただし、書き込みの失敗を黙って流すと、何日も気づけません。自動実行の失敗に気づく仕組みはタスクスケジューラの失敗に気づく方法 ― 自動実行を毎朝一覧で確かめるで扱っています。

まとめ