業務システム

Notion APIとは?データソース対応の使い方とレート制限・社内連携の実装【2026年10月】

Notion APIとは?データソース対応の使い方とレート制限・社内連携の実装【2026年10月】

Notion APIは、Notionのページやデータベースを外部のプログラムから読み書きするためのREST APIです。2025年9月の版でデータベースの中に「データソース」という層が入り、2026年10月時点の最新版は2026-03-11です。この記事では、認証方式の選び方、データソースIDを使ったクエリとページ作成、2026年9月に変わったレート制限と再送処理、Webhookの受信とMarkdownでの本文取得を、公式ドキュメントの数値と動かせるコードで解説し、個別開発すべき条件も整理しました。

まとめ:Notion APIは2026-03-11版とデータソースIDと毎分180回の枠で組む

新しく書くコードはNotion-Version: 2026-03-11を指定し、データベースIDではなくデータソースIDでクエリとページ作成を行います。日本語の解説記事の多くは/v1/databases/{id}/queryのままで、2025-09-03版より前の書き方です。チームで使う自動化には内部コネクション、自分専用のスクリプトには個人アクセストークンを使い分けます。

レート制限は2026年9月9日から接続ごとに60秒の固定窓になり、Business・Enterpriseは毎分600回、それ以外のプランは毎分180回です。429と529はRetry-Afterの秒数だけ待って再送し、書き込みの503は保存済みのことがあるため読み直してから判断します。変更の検知はWebhookに任せ、全件の定期取得は避ける設計が安全です。

Notion APIとは:ページ・データベース・データソースの外部操作用REST API

Notion APIの入口はhttps://api.notion.com/v1の1つで、要求も応答もJSONです。まずは扱うオブジェクトの単位と、2025年以降に変わった構造を押さえます。

api.notion.comで扱うページ・データソース・ブロック・ユーザーとできること

Notion APIのリファレンス冒頭によると、APIは可能な範囲でRESTの慣習に従い、GET・POST・PATCH・DELETEでページやデータベースを操作します。項目名はスネークケースで、IDはUUIDv4です。NotionのURLからコピーしたIDはハイフンを省いたまま渡せます。

オブジェクト 中身 主な操作
ページ 1件の文書か表の1行 作成・取得・項目の更新
データベース データソースの入れ物 作成・取得
データソース 列の定義を持つ表 クエリ・列の変更
ブロック 段落や見出しなど本文の部品 子要素の取得・追加
ユーザー メンバーとボット 一覧・取得

社内の連携でよく使うのは、データソースの行を条件で取り出す処理と、行を1件追加する処理の2つです。

2025-09-03版で入ったデータソースとデータベースIDの関係と旧記事との違い

2025-09-03版への移行ガイドは、データベースを「入れ物」、データソースを「表」として分けました。1つのデータベースに、列の定義が違う複数のデータソースを持てます。この変更で、クエリは/v1/data_sources/{id}/queryへ、ページ作成の親はdata_source_idへ移りました。

旧版の書き方のまま動かしている接続は、対象のデータベースにデータソースが追加された時点で、そのデータベースへのページ作成やクエリが失敗します。利用者が画面でデータソースを足しただけで連携が止まるので、既存のコードも移行の対象です。

2026-03-11版の3つの破壊的変更とNotion-Versionヘッダの指定方法

バージョン管理の公式ページでは、すべての要求にNotion-Versionヘッダが必須です。版は公開日の名前で、後方互換の無い変更があるときだけ新版が出ます。新しい項目やエンドポイントの追加は、古い版を指定していても同時に反映されます。

2026-03-11版の移行ガイドが挙げる変更は3つだけです。ブロック追加のafterがpositionに、ごみ箱を表すarchivedがin_trashに、transcriptionブロックがmeeting_notesに変わりました。JavaScriptの公式SDKはnotion-sdk-jsのv5.12.0から対応し、npmの最新は2026年10月22日時点で5.27.1系です。

応答に入るページのurlも、2026年6月にapp.notion.com/p/の形へ変わりました。URLからIDを切り出すコードは壊れるので、idの項目を使います。

認証方式の選び方:内部コネクションと個人アクセストークン、OAuthの違い

どの方式でも、トークンをAuthorization: Bearerヘッダで送る点は同じです。違うのは、誰の権限で動き、どのページに届くかです。

チームの自動化で選ぶ内部コネクションとAdd connectionsでの共有手順

認可の公式ガイドによると、内部コネクションは開発者ポータルでワークスペースの所有者が作り、設定画面のConfigurationタブでトークンを取得します。内部コネクションは独立したボットとして動き、共有されたページにしか届きません。

  1. 開発者ポータルで内部コネクションを作り、読み取り・書き込みなど必要な権限だけを選ぶ
  2. 対象のページかデータベースを開き、右上の「•••」から「Add connections」を選ぶ
  3. 作ったコネクションを検索して追加する(子ページにも引き継がれる)
  4. トークンを環境変数かシークレット管理に置き、コードには書かない

APIがobject_not_foundを返すとき、まず疑うのは2番目の共有漏れです。担当者が退職しても動き続けるので、業務の自動化は内部コネクションで作ります。

個人アクセストークンの有効期限と作成できる人、使ってはいけない場面

2026年5月12日からは、個人アクセストークン(PAT)も使えます。PATの公式ガイドによると、PATは作成した本人の権限で動き、Add connectionsの共有が要りません。有効期限は7日から1年の5段階で、指定しなければ1年で切れます。

方式 誰として動くか 向く用途
内部コネクション 専用のボット チームで使う業務の自動化
個人アクセストークン 作成した本人 手元のスクリプトや検証
公開コネクション(OAuth) 認可した各利用者 他社にも配る連携製品

PATを業務の連携に使ってはいけません。作成者がページへのアクセスを失えばPATも同じページに届かなくなり、異動や退職で連携が止まります。作成できる人はプランで決まり、Freeは所有者だけ、Plusは全メンバーです。

curlとPythonでデータソースのクエリとページ作成を試す手順

ここからは内部コネクションのトークンを環境変数NOTION_TOKENに入れた前提で進めます。最初にcurlでデータソースIDを取り、以降はPythonで書きます。

データベースIDからdata_source_idを取り出すGETリクエストと応答の読み方

データベースのURLに含まれる32桁の文字列がデータベースIDです。2025-09-03版以降、このIDを渡す取得APIは、中に含まれるデータソースの一覧を返します。

export NOTION_TOKEN="(内部コネクションのトークン)"
export DATABASE_ID="(データベースのURLに含まれる32桁のID)"

curl -s "https://api.notion.com/v1/databases/${DATABASE_ID}" \
  -H "Authorization: Bearer ${NOTION_TOKEN}" \
  -H "Notion-Version: 2026-03-11"

# 応答の抜粋
# {
#   "object": "database",
#   "id": "(データベースID)",
#   "data_sources": [
#     { "id": "(データソースID)", "name": "案件管理" }
#   ]
# }

データソースが1つならdata_sources[0].idを控えれば足ります。複数ある場合はnameで選びます。IDは利用者が名前を変えても変わらないので、名前ではなくIDを設定ファイルに持たせてください。

Pythonの条件・返却列の絞り込みと100件単位のページング処理

次のコードを動かすために追加インストールが必要なライブラリは、pip install requestsで入るrequestsだけです。ステータスが「完了」以外の行を、更新日時の新しい順に100件ずつ取り出します。データソースのクエリの公式リファレンスが勧めるとおり、filter_propertiesで返す列を絞り、応答を小さくしています。

import os
import random
import time

import requests

API = "https://api.notion.com/v1"
HEADERS = {
    "Authorization": f"Bearer {os.environ['NOTION_TOKEN']}",
    "Notion-Version": "2026-03-11",
    "Content-Type": "application/json",
}


def call(method, path, **kwargs):
    """429・529は待って再送し、5xxは読み取り(GET)のときだけ再送する"""
    for attempt in range(6):
        res = requests.request(method, API + path, headers=HEADERS, timeout=60, **kwargs)
        retryable = res.status_code in (429, 529) or (
            method == "GET" and res.status_code in (500, 502, 503, 504)
        )
        if res.status_code == 429:
            reason = res.json().get("additional_data", {}).get("rate_limit_reason")
            if reason == "public_api_request_blocked":
                retryable = False  # 接続が止められている。待っても直らない
        if not retryable or attempt == 5:
            res.raise_for_status()
            return res.json()
        wait = float(res.headers.get("Retry-After") or min(2 ** attempt, 30))
        time.sleep(wait + random.uniform(0, 0.25))  # ゆらぎを足して再送の集中を避ける


def open_tasks(data_source_id):
    """ステータスが「完了」以外の行を100件ずつ取り出す"""
    body = {
        "filter": {"property": "ステータス", "status": {"does_not_equal": "完了"}},
        "sorts": [{"timestamp": "last_edited_time", "direction": "descending"}],
        "page_size": 100,
    }
    while True:
        data = call(
            "POST",
            f"/data_sources/{data_source_id}/query",
            params={"filter_properties": ["title", "ステータス"]},
            json=body,
        )
        if data.get("request_status", {}).get("type") == "incomplete":
            raise RuntimeError("1クエリ1万件の上限に達した。期間で条件を分けて取り直す")
        yield from data["results"]
        if not data["has_more"]:
            break
        body["start_cursor"] = data["next_cursor"]


if __name__ == "__main__":
    for page in open_tasks(os.environ["DATA_SOURCE_ID"]):
        print(page["id"], page["url"])

page_sizeの上限は100で、続きはnext_cursorをstart_cursorに渡して取ります。カーソルは中身を解析せず、受け取った文字列をそのまま返してください。1つの条件で取れるのは1万件までで、超えるとhas_moreがfalseになり、request_statusに打ち切りの印が付きます。大きな表は作成日時の範囲で条件を分けて取ります。

data_source_idを親に指定して行を追加するページ作成と入力値の上限

行の追加はPOST /v1/pagesで、親にdata_source_idを指定します。前の節のcall関数をそのまま使えます。

def add_task(data_source_id, title, status="未着手"):
    """データソースに1行追加し、作られたページのIDを返す"""
    payload = {
        "parent": {"type": "data_source_id", "data_source_id": data_source_id},
        "properties": {
            "名前": {"title": [{"text": {"content": title[:2000]}}]},
            "ステータス": {"status": {"name": status}},
        },
    }
    return call("POST", "/pages", json=payload)["id"]

要求の上限を定めた公式ページでは、リッチテキストの本文とURLは2,000字、メールアドレスと電話番号は200字、複数選択・リレーション・ユーザーは1回100件までです。超えると400のvalidation_errorになるため、外部の値は送る前に切り詰めるか、超えた行を記録して知らせる処理を入れておきます。値を消すときは空文字ではなくnullです。

レート制限の実装:毎分180回・600回の枠と429・529・503で分ける再送処理

日本語の解説で「平均で毎秒3回」と書かれる上限は、2026年9月に数え方が変わりました。

2026年9月9日に変わった接続ごとの上限とワークスペース共有枠の数え方

Notion APIの変更履歴によると、2026年9月9日から接続ごとの上限は60秒の固定窓で数えるようになりました。BusinessとEnterpriseは毎分600回、それ以外のプランは毎分180回です。窓の中ではまとめて使っても均等に使ってもよく、待ち時間のRetry-Afterは最大60秒に縮みました。それまでは最大15分でした。

これとは別に設けられているのが、ワークスペース全体で共有する上限です。1つの接続が枠内でも、同じワークスペースの別の連携が使い切れば429が返り、この場合の待ち時間は1分を超えることがあります。社内に連携が増えるほどこの上限に当たりやすくなるため、夜間のバッチは実行時刻をずらして組んでください。上限の考え方の一般論はAPIレート制限(レートリミット)とは?5方式の比較と429設計・分散実装【2026年版】で整理しています。

rate_limit_reasonとRetry-Afterで待つ時間を決める再送の分岐

上限を超えた要求は429とrate_limitedを返し、応答本文のadditional_data.rate_limit_reasonで理由が分かります。前の章のcall関数は、公式ページの方針どおり次のように分岐しています。

  • 429と529(Notion側の過負荷)は、Retry-Afterの秒数だけ待って再送する
  • 理由がpublic_api_request_blockedの429は、接続が止められているので再送しない
  • 500・502・503・504は、GETとDELETEのように何度送っても結果が同じ要求だけ再送する
  • 400は要求の誤りなので、直さずに再送しない

ヘッダが無いときは1秒から倍々に延ばして最大30秒で止め、ゆらぎを足します。公式ページが示す方針は、再送の仕組みをワーカーごとに持たせず、1か所にまとめるというものです。指数バックオフの設計判断はリトライパターンとは?指数バックオフ・ジッター・リトライ予算の実装判断を解説で詳しく扱っています。

503でも保存済みのことがある書き込みを二重に実行しない確認手順

書き込みが保存されたのに、応答を組み立てる時間が足りずに503が返ることがあります。同じ要求を送り直すと、行が2件できます。

res = requests.post(API + "/pages", headers=HEADERS, json=payload, timeout=60)
if res.status_code == 503:
    extra = res.json().get("additional_data", {})
    created_id = extra.get("committed_resource_id")
    if created_id:
        # 保存は済んでいる。作り直さず、作られたページを読み直す
        page = call("GET", f"/pages/{created_id}")
    else:
        # 保存されたか分からない。同じ内容の行が無いかを検索してから判断する
        ...

ページやデータベースを作った要求なら、committed_resource_idに新しいIDが入ります。ブロックの追加で作られた子ブロックのIDが並ぶ項目は、committed_child_idsです。どちらも無い503は保存の有無が分からないため、外部システム側のIDを列に持たせておき、その列で検索してから再送を決めます。重複を止める考え方は冪等キーとは?Idempotency-Keyの発行・保存・再生の実装と二重課金の止め方を解説【2026年版】と同じです。

Freeプランの1,000ブロック上限で作成系のAPIが403になる条件

2026年9月1日から、Freeプランのブロック上限がREST APIにも適用されました。ワークスペースのブロック上限の公式ページによると、対象はメンバーが2人以上のFreeワークスペースで、累計1,000ブロックです。消しても枠は戻りません。

上限に達した書き込みは成功し、そこから3日の猶予が始まります。猶予が切れると、ページ作成やブロック追加は403のrestricted_resourceで失敗し、additional_data.block_limitが付きます。項目の値の更新や読み取りは使えますが、回数の上限ではないため待っても直りません。Freeで検証した連携を本番に移す前に、プランを確かめてください。

社内ツール連携の3パターン:Webhook受信とMarkdown取得、正データの置き場所

社内連携は、変更を受け取る、本文を取り出す、他のシステムと値をそろえる、の3つに分かれます。

Webhook受信時のトークンによる購読の有効化とHMAC-SHA256署名の検証

Webhookの公式リファレンスによると、購読を作るとNotionは1回だけverification_tokenを受信URLへ送ります。その値を開発者ポータルの画面に貼ると購読が有効になり、以後の通知には、このトークンを鍵にして本文をHMAC-SHA256にかけたX-Notion-Signatureが付きます。受信URLはHTTPSで外部から届く必要があり、localhostは使えません。

import hashlib
import hmac
import os

from flask import Flask, abort, request

app = Flask(__name__)
TOKEN = os.environ.get("NOTION_WEBHOOK_TOKEN", "").encode()


def enqueue(event_type, entity_id):
    # 実運用ではキューに積み、別プロセスでAPIを呼んで中身を取り直す
    print(event_type, entity_id)


@app.post("/notion/webhook")
def receive():
    body = request.get_json()
    if "verification_token" in body:
        # 購読の作成時に1回だけ届く。値はログに出さず、受信を確かめて画面に貼る
        return "", 200
    raw = request.get_data()  # 受け取ったままのバイト列で署名を計算する
    expected = "sha256=" + hmac.new(TOKEN, raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("X-Notion-Signature", "")):
        abort(401)
    enqueue(body["type"], body["entity"]["id"])
    return "", 200

通知に入るのは種類とページIDなどだけで、中身は受け取ったIDでAPIを呼んで取り直します。page.content_updatedのような編集の通知は、細かい編集をまとめるため少し遅れて届き、公式の動作確認の手順でも1〜2分待つよう案内されています。即時性が要る処理には向きません。受信側の作り全般はWebhookの作り方と実装方法|受信側の署名検証・冪等性と送信側の再送をNode.js・Pythonで解説を参照してください。

ページをMarkdownで取り出すエンドポイントで社内検索や生成AIへ渡す方法

社内のNotionを検索システムや生成AIの参照データにする場合、ブロックを1つずつたどる必要はなくなりました。ページをMarkdownで取得する公式リファレンスのGET /v1/pages/{page_id}/markdownは、本文を1つのMarkdown文字列で返します。

curl -s "https://api.notion.com/v1/pages/${PAGE_ID}/markdown" \
  -H "Authorization: Bearer ${NOTION_TOKEN}" \
  -H "Notion-Version: 2026-03-11"

# 応答の項目
# object: "page_markdown"
# markdown: ページ本文のMarkdown
# truncated: 約2万ブロックを超えて切り詰められたら true
# unknown_block_ids: 読めなかったブロックのID(最大100件)

共有されていない子ページやブックマーク、埋め込みはunknownのタグになります。truncatedがtrueなら、unknown_block_idsのIDを同じエンドポイントに渡して、残りの本文を取得してください。AIのクライアントから対話的にNotionを読むだけなら、APIを書かずにNotion MCPサーバーのURL・接続方法まとめ|mcp.notion.com への接続手順【2026年最新】の方法でつなぐほうが早く済みます。

基幹システムとNotionの定期同期で正データをどちらに置くかの決め方

顧客や案件のマスタをNotionと基幹システムの両方に持つなら、どちらを正にするかを最初に決めます。もう一方を読み取り専用の写しにすれば同期は片方向で済み、両方で編集させると衝突を解く規則が要って開発費が一段上がります。

Notionを正にする場合は、Webhookで変更を受けて基幹側へ写します。基幹側を正にする場合は、変更があった行だけをadd_taskのような関数で追加・更新し、毎回の全件書き込みは避けます。毎分180回の枠なら、1行1回の書き込みで1時間に最大1万800行です。共有枠の分を差し引いて、半分程度を上限に見積もると安全です。経路の選び方を発注者の目線でまとめたNotion連携アプリ一覧と連携方法|Slack・Jira・GitHub・国産ツールの対応状況【2026年10月】も、設計前の確認に使えます。

受託開発の視点で見る、Notion APIで個別開発すべき条件と見送る場面

ここからは、連携開発を受ける立場からの判断です。分かれ目は件数と、失敗したときの扱いにあります。

個別開発を選ぶ条件:1日数千行の同期・二重登録の防止・失敗行の再送

次のどれかに当てはまるなら、APIで個別に開発します。1日に数千行以上を同期し、毎分180回の枠と共有枠を自分で配分したいとき。503や再送で行が二重にできることを、外部IDの照合で確実に止めたいとき。失敗した行だけを再送し、理由を記録して担当者に知らせたいとき。

個別開発で継続して必要になる保守は、版の更新、トークンの管理、Webhookの監視です。2025-09-03版と2026-03-11版のように、1年に満たない間隔で書き換えが要る変更が出た実績もあります。要件の整理から実装と保守までをまとめて頼みたい場合は、API開発・システム連携で連携開発を受け付けています。

個別開発を見送る場面:1日数百件までの転記と標準の連携機能で足りる用途

フォームの回答をNotionの表に1行追加する、Notionの更新をSlackに知らせる、といった1日数百件までの転記に個別開発は過剰です。iPaaSなら画面の設定で組め、担当者が自分で直せます。料金の数え方はZapierとは?読み方・できること・料金とタスク課金の仕組み【2026年版】で確認してください。

SlackやJira、GitHubのように標準の連携機能がある製品もまずはそれで始め、届かない部分だけをAPIで作り足すのが費用を抑える進め方です。

Notion APIのよくある質問:無料プランでの利用・Python・MCPとの違い

Notion APIで連携を作る前によく寄せられる質問に、2026年10月時点の公式ドキュメントをもとに回答します。

Notion APIは無料プランでも使えますか?

使えます。Freeのワークスペースでも内部コネクションを作ってAPIを呼べ、接続ごとの上限は毎分180回です。ただしメンバーが2人以上のFreeワークスペースは、累計1,000ブロックを超えて3日の猶予が過ぎると、ページ作成などの書き込みが403になります。本番の連携は有料プランで動かす前提で計画してください。

PythonでNotion APIを使うにはどのライブラリを選べばよいですか?

Notionが公開している公式SDKはJavaScript向けの@notionhq/clientです。Pythonでは、notion-sdk-pyがPyPIでnotion-clientとして配布されており、2026年5月公開の3.1.0系はデータソースのクエリに対応しています。依存を増やしたくないなら、この記事のようにrequestsで直接呼んでも数十行で書けます。

NotionのAPIとNotion MCPはどう使い分ければよいですか?

自社のプログラムが決まった処理を繰り返すならAPI、ClaudeやChatGPTのようなAIの画面から人が対話的にNotionを読み書きするならMCPです。MCPはOAuthで接続し、ツールの応答はNotion-Versionの版管理の対象外です。定期実行の同期やWebhookの受信のように、入出力の形を固定したい処理にはAPIを選んでください。

古い記事のdatabases/{id}/queryのコードはまだ動きますか?

旧版を指定していれば、データソースが1つのデータベースでは動きます。公式の説明では、古い版の提供を終える予定は今のところ無いという方針です。ただし利用者がデータベースに2つ目のデータソースを足すと、旧版のクエリやページ作成は失敗します。JavaScriptのSDKはv5.0.0から2022-06-28以前の版を扱わなくなったので、SDKの更新と合わせて移すのが確実です。

APIがobject_not_foundを返すときは何を確かめればよいですか?

最初に、対象のページかデータベースがコネクションに共有されているかを確かめます。内部コネクションは、Add connectionsで追加したページとその子ページにしか届きません。次に、データベースIDとデータソースIDを取り違えていないかを見ます。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.07 テックブログ ファインズの不正アクセスと153万件の予約者情報|予約システムを使う店舗の報告と通知の手順
  2. 2025.08.14 コラム トラクションとは?ビジネスでの意味・指標の目安とgain tractionの使い方
  3. 2026.10.15 テックブログ 銀のさら不正アクセス:最大650万件の対象確認と窓口、パスワード変更・退会の手順
  4. 2026.10.19 テックブログ コミックシーモア不正アクセスで会員情報流出:再開時期・パスワード再設定と運営側のログ点検
  5. 2026.10.16 テックブログ バーガーキング情報漏洩どうしたらいい?約1,118万件の対象確認・補償・退会とメールの真偽

RELATED POSTS 関連記事

目次