Notion

Notion API 入門 2026年版|設定と自動化

更新: AIビルダー編集部
Notion

Notion API 入門 2026年版|設定と自動化

Notion APIを使った自動化は、インテグレーションを作るだけでは動かず、対象ページやデータソースへの共有まで終えてはじめて一歩進みます。初回セットアップでトークン発行までは順調でも、そこで止まって詰まる場面を何度も見てきました。この記事は“共有まで完了して動く”状態に最短で届く流れに絞っています。

Notion APIを使った自動化は、インテグレーションを作るだけでは動かず、対象ページやデータソースへの共有まで終えてはじめて一歩進みます。
初回セットアップでトークン発行までは順調でも、そこで止まって詰まる場面を何度も見てきました。
この記事は“共有まで完了して動く”状態に最短で届く流れに絞っています。
対象は、個人や社内の運用でNotionと外部ツールをつなぎたい人、特にJavaScriptや『Google Apps Script』で最小コードから始めたい人です。
『Start building with the Notion API』に沿って「読み取り」と「ページ作成」をまず1回成功させます。
さらに、平均3req/sの制限、429時のRetry-After、100件上限のページネーション、APIバージョン固定の勘所まで押さえます。
古い記事に残る database 前提の実装をそのまま写すのではなく、2025-09-03 と 2026-03-11 の変更点も踏まえて、data source 概念を見失わない形で、将来壊れにくい接続方法を掴んでいきます。

Notion APIでできることと、この記事でやること

Notion APIでできること一覧

Notion APIで触れる対象を先に俯瞰しておくと、どこまで自動化できるのかが見えます。
ページの作成や更新はもちろん、ページ内のブロック追加・取得・並べ替えに関わる操作、データベースまたはデータソースの検索やレコード登録、ワークスペース内の検索、ユーザー情報の取得、コメントの読み書きまでカバーされています。
単に「Notionに1件追加する」だけのAPIではなく、ドキュメント運用とデータ運用の両方をまたいで扱えるのが特徴です。

実務で触る頻度が高いのは、ページ、ブロック、データソースの3つです。
たとえば『Google Apps Script』から日報ページを1件作る、既存ページにブロックを追記する、フォームの回答をデータソースへ1件登録する、といった流れはこの範囲で完結します。
検索APIを使えば、タイトルや条件に応じて対象ページを探し、その結果を起点に更新処理へつなげられます。
ページはルートブロックでもあるので、「ページを読む」と「ブロックの子要素をたどる」の感覚がつながると、本文抽出や追記処理の見通しも立ちます。

公開アプリを作る文脈では、OAuth 2.0 を使うパブリックインテグレーションも視野に入ります。
複数ユーザーに配布するSaaS連携やリンクプレビュー系の機能ではこちらが前提です。
また、変更検知の入口としてWebhookも存在します。
共有されたページやデータベースの更新を受けて処理を走らせる設計まで含めると、単発のAPI呼び出しから継続運用まで伸ばせます。
Notion 。

ここでひとつ意識したいのが、古い記事に残っている「database_id中心」の説明です。
2025-09-03のアップグレードでは multi-source databases への対応に伴い、場面によっては database_id から data_source_id ベースの見方へ切り替える必要が出てきました。
さらに 2026-03-11 では破壊的変更が入り、append の位置指定、archived から in_trashtranscription から meeting_notes への変更が加わっています。
APIは一度つないで終わりではなく、どのバージョン前提で書かれたコードかまで読まないと、サンプルがそのまま動かない場面が出ます。

Apps Script  |  Google for Developers developers.google.com

この記事の到達点と進め方

この記事では、公開配布向けのOAuth連携ではなく、内部インテグレーションを起点に話を進めます。
対象は単一ワークスペース内の自動化です。
最初にインテグレーションを作成し、発行されたトークンを取得し、対象ページまたは対象データソースに接続し、トークンを環境変数へ保存したうえで、最小の読み取りと書き込みまで通します。
そのうえで、運用時に避けて通れないAPIバージョン固定やレート制限の扱いまで一気通貫で押さえます。

筆者は何ができるかだけを眺めている段階だと設計が広がりすぎて止まりやすいと感じています。
そこで最初のゴールを「1件読んで1件書く」に固定しています。
Notionの自動化は、検索、判定、分岐、差分更新まで広げようとすると急に複雑になりますが、最初の1回は読むか書くかのどちらか、できれば両方を最小単位で通したほうが、認証・共有・権限・レスポンス形式の輪郭が一度に見えます。

進め方もその前提に合わせます。
まずMy integrationsで内部インテグレーションを作成し、ワークスペースに対して必要な権限を持つ bot を用意します。
次にシークレットトークンを受け取り、コードへ直書きせず環境変数へ保存します。
NotionAPIキーはソースコードに埋め込まず、安全な方法で保管する運用が前提です。
GitHub や『Google Apps Script』の共有プロジェクトでこの線引きを曖昧にすると、動いたあとで回収に手間がかかります。

そのあとで、対象ページや対象データソースにインテグレーションを接続します。
ここが初回の詰まりどころで、トークンを持っていても共有されていないページにはアクセスできません。
前のセクションで触れた通り、インテグレーション作成だけでは到達できず、Notion側で対象を共有して初めてAPIの対象になります。
接続が済んだら、まずは1件取得、続いて1件追加という最小コードを動かします。
読者ゴールは、初回の自動化を自分の環境で再現できること、そして Notion-Version を固定する理由と、平均3リクエスト/秒の制限に備えて 429 時は Retry-After に従う理由を説明できることです。
Request limits - Notion Docsまで含めて読むと、このあたりの設計意図がつながります。

💡 Tip

この段階では「一覧を全部同期する」「差分更新を入れる」まで広げないほうが進みます。1件取得と1件追加が通れば、認証、共有、レスポンスの解釈、書き込み形式の確認が一度に終わるからです。 [!TIP] この段階では「一覧を全部同期する」「差分更新を入れる」まで広げないほうが進みます。まずは 1 件取得と 1 件追加が通れば、認証・共有・レスポンスの解釈・書き込み形式の確認が一度に終わるからです。

対象読者・前提スキル

想定しているのは、Notionを手作業だけで回すのがつらくなってきて、外部ツールやスクリプトとつなぎたい人です。
個人運用でも社内運用でも構いませんが、まず向いているのは公開アプリ開発者より、自分のワークスペースで動く仕組みを作りたい人です。
JavaScriptで簡単なHTTPリクエストを読める人や、『Google Apps Script』で UrlFetchApp.fetch にヘッダーを渡したことがある人なら入りやすい構成にしています。

前提スキルは高くありません。
必要なのは、APIに Authorization ヘッダーを付ける、JSONを送受信する、環境変数にトークンを入れる、という基礎です。
『Google Apps Script』を使う場合も、UrlFetchApp でカスタムヘッダーを付けられるので、Notion APIの最初の検証には十分です。
逆に、OAuth 2.0 のフロー設計やマルチテナントの認可設計が必要な公開アプリの文脈はこの記事の外に置きます。
そこを混ぜると、内部インテグレーションの初回学習と論点がずれてしまうからです。

また、古いチュートリアルを一度触って混乱した経験がある人にも、このセクションの整理は役立ちます。
最新仕様では data source の概念を無視できず、APIバージョンによって破壊的変更も入ります。
database_id と書かれた記事を見つけたときに、「その説明はいつの仕様か」を見分けられるだけで、調査の時間がだいぶ減ります。
SDKを使う場合も、2026-03-11 のバージョンは @notionhq/client v5.12.0 がサポートしています。
コードを書く前にその前提をそろえておくと、サンプルと実行結果の差が小さくなります。

このあとの流れでは、インテグレーション作成、トークン取得、対象ページまたはデータソースへの接続、環境変数への保存という最初の関門を順番に越えていきます。
最初のつまずきは「APIを叩く技術」より「Notion側の共有モデル」を見落とすことから起きるので、そこを外さない構成にしています。

始める前に知っておきたい基本用語

用語の基礎リスト

ここは、後の手順で何度も出てくる言葉を先にそろえるパートです。
Notion APIは単語の意味がつながると一気に見通しがよくなります。
特に「ページ」「ブロック」「データベース」「データソース」は、古い記事と新しい公式ドキュメントで見え方が少し違うので、最初に輪郭を合わせておくと途中で迷いません。

ワークスペースは、チームや個人がNotionを使う作業領域全体です。ページやデータベース、ユーザー、インテグレーションの所属先として考えると整理できます。

インテグレーションは、Notion APIを使って外部プログラムとNotionをつなぐための接続設定です。
My integrationsで作成し、APIを呼ぶためのシークレットトークンや権限設定を持ちます。

botは、インテグレーションにひもづいて動くAPI上の実行主体です。実際にはこのbotが、共有されたページやデータソースに対して読み書きを行います。

ページは、Notion上の1つのコンテンツ単位です。見た目はドキュメントに近いですが、APIでは本文や子要素を持つ構造化オブジェクトとして扱います。

ブロックは、ページの中身を作る最小単位です。段落、見出し、箇条書き、画像などがブロックとして並び、入れ子にもできます。

データベースは、複数のページをプロパティ付きで整理するための入れ物です。表やボードのように見えることが多く、タスク管理や顧客管理の土台になります。

データソースは、2025-09-03以降の考え方で中心になる、実際にクエリ対象となるデータ集合です。
最新仕様ではdata_source_idを前提に読む場面があります。

この違いは、初心者ほど早めに知っておいたほうが流れが止まりません。
古い技術ブログを読んでいると「database IDを取ればそのまま進める」と見えます。
『Upgrade guide 2025-09-03』で案内されている通り、最新の仕様ではdatabaseという見た目の背後にdata sourceという実体があると捉えたほうが自然です。
実際に触ってみると、画面上の「データベース」とAPI上の「どこに問い合わせるか」が1対1に見えない場面があり、この時点で言葉を分けて理解していると混乱が減ります。

Upgrade guide - Notion Docs developers.notion.com

内部/公開インテグレーションの違い

インテグレーションには大きく分けて、内部インテグレーションパブリックインテグレーションがあります。
同じNotion APIを使う点は共通ですが、配布の前提と認可の仕組みが違います。

内部インテグレーションは、1つのワークスペース内で使う私的な自動化向けです。
個人のNotionと『Google Apps Script』をつなぐ、社内の定期同期を組む、といった用途ならこちらが素直です。
トークンを発行し、そのインテグレーションを対象ページやデータソースに接続して使います。
利用範囲が最初から限定されているので、認証フローも短く、初回の成功体験を作りやすい構成です。

一方のパブリックインテグレーションは、不特定または複数のユーザーに配布する公開連携向けです。
SaaSから複数のNotionワークスペースへ接続するアプリや、広く提供する連携機能ではこちらを選びます。
こちらはOAuth 2.0ベースで認可を行い、ユーザーごとの接続と許可を受けてアクセストークンを扱います。
『Start building with the Notion API』でも、内部は単一ワークスペース向け、公開はOAuthを伴う配布向けとして整理されています。

違いを3点に絞ると、次のように見るとつかみやすいのが利点です。

項目内部インテグレーションパブリックインテグレーション
配布の前提自分または社内の特定ワークスペース複数ユーザー・複数ワークスペースへ公開
認可方式発行した内部トークンを使うOAuth 2.0でユーザー同意を得る
向いている用途個人運用、社内自動化、検証公開アプリ、SaaS連携、外部提供

本記事が内部インテグレーションを採用する理由もここにあります。
最初の1回で必要なのは、OAuthの設計よりも「トークンを発行し、対象ページに接続し、読み書きが通る」ことだからです。
個人利用や社内利用では、この順番のほうが理解の段差が小さくなります。
公開連携まで同時に追うと、リダイレクトURI、同意画面、トークン交換といった別の論点が一気に増えます。
まず内部で動かして構造をつかむほうが、後から公開型に広げるときも土台がぶれません。

ページとブロックの関係(概念図解テキスト版)

Notionの画面だけ見ていると、ページは1枚のドキュメントに見えます。
ですがAPIの理解では、その見方だけだと途中で引っかかります。
ページは本文を持つ箱というより、ブロックの木構造のルートとして捉えると全体がつながります。

文章で図にすると、こんなイメージです。

  1. ページがいちばん上の親です。
  2. ページの直下に、段落や見出しやリスト項目といったブロックが並びます。
  3. そのブロックの中には、さらに子ブロックを持てるものがあります。
  4. つまり、ページ配下にブロックがぶら下がり、必要ならその下にもブロックが続く木構造になります。

この見方に切り替わると、後で出てくる「block childrenを取得する」という発想が自然になります。
筆者も最初はページを「1つの完成した文書オブジェクト」として見ていたのですが、ページはブロックの集合のルートだと腑に落ちてから、取得系エンドポイントの挙動が一気に追えるようになりました。
ページ本体のメタ情報を取る処理と、本文の中身をたどる処理が分かれている理由も、その構造なら素直に理解できます。

たとえば、ある会議メモのページがあるとします。APIの頭の中の形は、次のように考えると整理できます。

ページ └ 段落ブロック └ 見出しブロック └ 箇条書きブロック  └ 子リストブロック └ トグルブロック  └ 段落ブロック

この形を知っていると、「ページを取得したのに本文が全部入っていない」と戸惑いにくくなります。
ページはページ、本文の列はブロック列として取る、という分担だからです。
Notion APIはオブジェクトを平坦にまとめず、構造を保ったまま返してくるので、見た目のドキュメント感覚よりも「親子関係のあるデータ」として見るほうが実装に直結します。

データベース系との関係も同じ発想で読むとつながります。
データベースやデータソースの中に並ぶ各行は、APIでは多くの場合「プロパティを持つページ」です。
つまり、本文を書けるページという性質と、一覧上の1レコードという性質が重なっています。
この二重性を知らないまま触ると、ページとデータベース項目を別物だと感じがちです。
実際には「データソースに属するページ」と考えると、プロパティ更新と本文ブロック更新の違いも見通せます。

Notion APIの初期設定手順

インテグレーション作成と権限

最初の手順は、NotionのMy integrationsで内部インテグレーションを作ることです。
個人運用や社内自動化の初回検証なら、公開用ではなく内部インテグレーションを選ぶと流れが短くなります。
Start building with the Notion APIでも、単一ワークスペース内で使う連携はこの始め方が基準になっています。

作成時は、名前を付けて対象ワークスペースを選び、必要最小限の権限だけを有効にします。
たとえば読み取り確認から始めるなら、最初から書き込み権限まで広げず、ページやデータソースの読み取り中心で十分です。
ここで発行されるシークレットトークンが、以後のAPI呼び出しで使う認証情報になります。

トークンは作成直後の扱いでつまずきやすい箇所です。
画面に出た値を安全な場所に退避し、コードやメモ帳にそのまま貼り回さない運用に切り替えておくと、あとで修正範囲が増えません。
権限も同じで、最初は狭く、必要な処理が見えた段階で足すほうが事故を減らせます。

ページ/データソースへの共有

インテグレーションを作っただけでは、まだAPIは対象に届きません。
実際に触りたいテスト用ページ、または既存のデータソースを開き、共有から作成したインテグレーションを追加して、接続先として明示的に共有します。
ここを飛ばすと、トークンが正しくても取得結果が返らず、最初の一歩で止まります。

この共有は、親だけ設定すれば全部通ると思い込みやすいのですが、実際にはそこが落とし穴です。
筆者は親ページだけ共有した状態で進めてしまい、子ページや配下のデータソースにアクセスした途端、403相当の状態で止まる場面に何度か当たりました。
最初に疑うべきなのは認証ロジックよりも、“対象にきちんと共有されたか”です。
親ページの下にある子ページや、ページ内に置いたデータソースは、必要に応じて個別に共有が要ります。

あわせて、ここで対象IDも控えておくと後の実装が滑らかです。
ページはURLから page_id を読み取れます。
データソースはUI上の表示でIDを確認する流れになり、旧来の記事でよく見かける database_id とは表記が変わっている点に注意が必要です。
Upgrade guide 2025-09-03で触れられている通り、古い解説をそのままなぞると、名称の違いで手が止まりやすくなります。

環境変数に保存(直書き禁止)

取得したトークンは、コードへ直書きせず環境変数に保存します。
これだけで、Gitへの誤コミット、画面共有中の露出、別環境への持ち出しといった事故をだいぶ減らせます。
Best practices for handling API keysでも、APIキーはソースコードに埋め込まず、環境変数や安全なシークレット管理で扱う方針が示されています。

たとえば macOS/Linux なら次の形です。

export NOTION_TOKEN='secret_xxx'

Windows PowerShellならこちらです。

$env:NOTION_TOKEN='secret_xxx'

プロジェクト内では process.env.NOTION_TOKEN のように参照し、値そのものはログに出さない構成にしておくと扱いが安定します。
GASでも同じ発想で、トークン文字列をソースに置かず、スクリプトプロパティなどコード外の保存先へ分けるのが定石です。

トークンのローテーションも、最初に流れだけ頭に入れておくと慌てません。
新しいトークンを発行して保存先を差し替え、動作確認後に古いトークンを無効化する順番にすると、止まる時間を短くできます。
いきなり削除してから差し替えると、どこで失敗したのか追いにくくなります。

💡 Tip

認証エラーに見えても、実際は「共有漏れ」か「古いトークン参照」のどちらかで止まることが多いです。実装を疑う前に、共有先と環境変数の中身が今の設定と一致しているかを切り分けると、復旧までの手数が減ります。

SDK/GASの準備

準備ができたら、呼び出し側を整えます。
Node.jsなら @notionhq/client を入れるのが最短です。
APIバージョンは 2026-03-11 を明示する前提で進めます。
このバージョンは @notionhq/client v5.12.0 でサポートされています。
バージョンを固定しておくと、後から仕様変更を踏んだときの切り分けが楽になります。

npm install @notionhq/client

npm install @notionhq/client
import { Client } from '@notionhq/client';

const notion = new Client({
  auth: process.env.NOTION_TOKEN,
  // `notionVersion` の指定方法は SDK のバージョン依存の場合があります。
  notionVersion: '2026-03-11',
(注) UrlFetchApp のタイムアウトに関する具体的な秒数は公式に明示されていないことがあるため、記事中で見つかる「約50秒」等のコミュニティ実測値は参考値として扱ってください。設計では再試行や分割処理を前提にすることを推奨します。

function getNotionPage(pageId) { // スクリプトプロパティに保存した NOTION_TOKEN を取得します const token = PropertiesService.getScriptProperties().getProperty('NOTION_TOKEN'); if (!token) { Logger.log('ERROR: NOTION_TOKEN が設定されていません。
'); return; } const url = };

const options = { method: 'get', headers: { Authorization: Bearer ${token}, 'Notion-Version': '2026-03-11', // APIバージョンを固定 'Content-Type': 'application/json', }, muteHttpExceptions: true, };

const res = UrlFetchApp.fetch(url, options); Logger.log(res.getResponseCode()); Logger.log(res.getContentText()); }

    },
    muteHttpExceptions: true,
  });

  Logger.log(res.getResponseCode());
  Logger.log(res.getContentText());
}

/*
> [!WARNING]
> - UrlFetchApp のタイムアウト値は公式ドキュメントで明示されていないため、コミュニティ報告に頼る設計は避けてください。

- 外部APIが遅い場合は分割・再試行・並列化など、タイムアウトに依存しない設計を検討してください。
*/

この段階では、本文ブロックを全部たどるところまで急がなくて構いません。
まずは共有済みページのタイトルなど、メタ情報の取得が通るかを見ます。
そこが返れば初期設定は通過です。
その先で一覧取得や更新に進むと、どこから崩れたのかを追い詰めやすくなります。

最小サンプルで試す:まずは読み取り

JavaScript SDKでページ取得

最初の到達点は、共有済みのページを1件だけ読める状態にすることです。
更新や一覧取得まで一気に進めるより、まず page_id を指定してタイトルと作成日時が返るかを見る方が、認証・共有・ID指定・バージョン指定の4点を一度に確認できます。

Notionのバージョニングは明示しておく方が後で崩れません。Versioning - Notion 。この前提で、最小コードは次の形です。

import { Client } from '@notionhq/client';

const notion = new Client({ // Notionクライアント(API操作用)
  auth: process.env.NOTION_TOKEN, // シークレットは環境変数で管理
  notionVersion: '2026-03-11',
});

const pageId = process.env.NOTION_PAGE_ID;

function extractTitle(page) {
  const titleProp = Object.values(page.properties).find(
    (prop) => prop.type === 'title'
  );

  return titleProp?.title?.map((t) => t.plain_text).join('') || '(タイトルなし)';
}

async function main() {
  const page = await notion.pages.retrieve({ page_id: pageId });

  console.log('title:', extractTitle(page));
  console.log('created_time:', page.created_time);
}

main().catch(console.error);

このコードで titlecreated_time が出れば、最初の接続確認としては十分です。
私自身、初回はIDのコピーでつまずきました。
NotionのURL末尾に見える文字列と、APIに渡す page_id の形式が頭の中で一致しておらず、ハイフン付きとハイフンなしを混同して失敗しがちでした。
ここは実装より入力値の確認で解決することが多く、URL末尾のIDをどの形で使っているかを揃えると通りが早いです。

一覧取得に進む前に知っておきたいのは、1回のページネーション取得で取れる件数に上限があることです。
データソースや検索結果をまとめて読む場面では、1回で最大100件までに区切られ、続きは next_cursor を使ってたどります。
最初の検証では1件取得で流れを確認し、そのあと一覧へ広げる方が切り分けが楽です。

fetch/GASでページ取得

『Google Apps Script』や素の fetch では、AuthorizationNotion-Version を自前で付けて呼びます。
SDKなしでも手順は単純で、URL、ヘッダー、page_id の3点が合っていればページのメタ情報まで届きます。

まずは fetch の例です。

const pageId = process.env.NOTION_PAGE_ID;
const token = process.env.NOTION_TOKEN;

async function getPage() {
  const res = await fetch(` {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Notion-Version': '2026-03-11', // Notion APIバージョン指定
      'Content-Type': 'application/json', // リクエストはJSON
    },

const pageId = process.env.NOTION_PAGE_ID; const token = process.env.NOTION_TOKEN;

async function getPage() { const res = await fetch(}, { method: 'GET', headers: { 'Authorization': Bearer ${token}, 'Notion-Version': '2026-03-11', // Notion APIバージョン指定 'Content-Type': 'application/json', // リクエストはJSON }, });

const data = await res.json();

console.log('status:', res.status); console.log('url:', data.url); console.log('created_time:', data.created_time); }

getPage().catch(console.error);

function getNotionPageMeta() { const pageId = PropertiesService.getScriptProperties().getProperty('NOTION_PAGE_ID');

if (!pageId || !token) {

Logger.log('ERROR: NOTION_PAGE_ID または NOTION_TOKEN が設定されていません。
スクリプトプロパティを確認してください。
'); return; } const url = };

const res = UrlFetchApp.fetch(url, { method: 'get', headers: { Authorization: Bearer ${token}, 'Notion-Version': '2026-03-11', // バージョン固定(変更不可) 'Content-Type': 'application/json', }, muteHttpExceptions: true, });

const status = res.getResponseCode(); const text = res.getContentText();

try { const data = JSON.parse(text); Logger.log(status: ${status}); Logger.log(url: ${data.url}); Logger.log(created_time: ${data.created_time}); } catch (e) { // レスポンスがJSONでない場合は生のテキストをログに出す Logger.log(status: ${status}); Logger.log(responseText: ${text}); }

      Authorization: `Bearer ${token}`,
      'Notion-Version': '2026-03-11', // バージョン固定(変更不可)
      'Content-Type': 'application/json',
    },
    muteHttpExceptions: true,
  });

  const status = res.getResponseCode();
  const data = JSON.parse(res.getContentText());

  Logger.log(`status: ${status}`);
  Logger.log(`url: ${data.url}`);
  Logger.log(`created_time: ${data.created_time}`);
}

fetch と 『Google Apps Script』のどちらでも、200が返ってページの url やタイトル用プロパティが見えれば接続確認は通過です。
逆に失敗したときは、コード全体を見直す前に3点を先に切ると詰まりません。
共有権限が付いているか、page_id が正しいか、Notion-Version が意図した値になっているかです。
ここを外すと、認証エラーやオブジェクト未検出のように見えても実態は設定漏れ、ということがよくあります。

ℹ️ Note

'Request limits - Notion Docs' の目安は平均で約3リクエスト/秒です。429 の際は必ず Retry-After を参照して再試行する運用にしてください。

成功時レスポンスで見るポイント

ページ取得が成功すると、レスポンスは1つのページオブジェクトとして返ります。
先頭で見ておくと判断が早いのは、objectidcreated_timelast_edited_timeurlproperties あたりです。
objectpage で、idurl があり、properties の中にタイトル用のプロパティが入っていれば、対象ページを正しくつかめています。

タイトルはレスポンスのトップレベルには出ず、properties の中にある type: "title" のプロパティから取り出します。
初見だと title というキー名を決め打ちしたくなりますが、ここはデータベースやページの設計によって名前が違います。
見るべきなのはプロパティ名そのものではなく、どのプロパティが title 型かです。
そこさえ拾えれば、タイトル列の表示名が「名前」でも「案件名」でも処理は通ります。

成功判定もこの形にしておくと明快です。
HTTPステータスが200で、ページのタイトル用プロパティまたは url が取得できること。
この2つがそろえば、接続はできています。
もし200以外なら、権限、ID、バージョンの3点から先に疑うと、無駄にロジックを書き換えずに済みます。
特にIDは、見た目は合っているのに1文字足りない、ハイフンの扱いがずれている、という初歩的なミスが混ざりやすく、実際にはここで止まることが多いです。

この段階でレスポンス全体を丁寧に眺めておくと、その後にデータソース一覧やページ一覧へ広げたときも迷いません。
ページ単体の形が頭に入っていると、一覧レスポンスで results の各要素に何が入るか、has_morenext_cursor をどう読むかが自然につながります。

最小サンプルで試す:ページ作成とデータ追加

子ページの最小作成例

読み取りで接続確認ができたら、次は「1件だけ作る」に進むと流れがつかみやすくなります。
実際、私も最初は親ページ直下に1件だけ子ページを作るところから始めました。
ここでレスポンスの形と権限の通り方を確認してから、次にプロパティ付きの登録へ広げると、頭の切り替えが少なく済みました。

まず試すなら、すでにインテグレーションを共有済みの親ページ配下に、シンプルな子ページを1つ作り、その本文に見出しブロックを1つ追加する構成が最短です。
ページそのものを作るリクエストと、本文ブロックを追加するリクエストは分かれているので、最小サンプルでも「ページ作成」と「ブロック追加」を分けて考えると後で混乱しません。

JavaScriptの公式SDKで書くと、コードは次の形です。

import { Client } from "@notionhq/client";

const notion = new Client({ // Notionクライアント(ページ作成用)
  auth: process.env.NOTION_TOKEN, // 環境変数NOTION_TOKENを使用
});

async function createChildPage() {
  const parentPageId = process.env.NOTION_PARENT_PAGE_ID!;

  const page = await notion.pages.create({
    parent: {
      type: "page_id",
      page_id: parentPageId,
    },
    properties: {
      title: {
        title: [
          {
            type: "text",
            text: {
              content: "API最小作成テスト",
            },
        ],
      },
  });

  await notion.blocks.children.append({
    block_id: page.id,
    children: [
      {
        object: "block",
        type: "heading_2",
        heading_2: {
          rich_text: [
            {
              type: "text",
              text: {
                content: "はじめて作った見出し",
              },
          ],
        },
    ],
    // 2026-03-11 以降は append 時に position 指定が導入されています。
    // 既存ブロックの前後どこへ差し込むかを制御したい場面で使います。
  });

  console.log(page.id);
}

createChildPage();

このサンプルで見たいのは、子ページが親ページの下に作成されることと、その中に heading_2 ブロックが1つ入ることです。
ページタイトルは properties.title で渡し、本文は blocks.children.append で後から足しています。
Notionの画面で結果を見ると、APIが「ページという箱」と「中身のブロック」を別レイヤーで扱っていることが直感的につかめます。

SDKを使う場合、APIバージョンの追従状況も気にしておくと詰まりません。
バージョンごとの差分が整理されており、2026-03-11@notionhq/client v5.12.0でサポートされています。
新しいバージョンに合わせた記法へ寄せておくと、あとからサンプルを流用するときに手直しが減ります。

データソースへの追加(概念と注意点)

子ページの作成が確認できたら、その次に自然につながるのが「表に1行追加する」感覚での登録です。
Notionでは、タスク一覧や問い合わせ台帳のような構造化データを扱うとき、ページを単体で増やすより、データソースに対してプロパティ付きで1件登録するほうが運用に乗せやすくなります。
1行追加という見え方ですが、実体としてはプロパティを持ったページをそのデータソース配下に作っているイメージです。

考え方はシンプルで、流れはだいたい3段階です。
対象のデータソースを特定し、必須プロパティの型を確認し、その型に合わせて値を組み立てて1件作成します。
タイトル、日付、セレクト、チェックボックスのどれを使っているかを先に把握しておくと、送るJSONがすぐ決まります。
ここを見ないまま実装を始めると、名前は合っているのに型が合わず、登録だけ失敗するケースに当たりやすいのが利点です。

一点だけ意識しておきたいのが、旧来の database ベースの見方から data source ベースへの移行です。
Upgrade guide 2025-09-03で案内されている通り、2025-09-03の移行以降は、環境によって database_id ではなく data_source_id を指定する必要があるケースがあります。
以前のサンプルをそのまま持ってくると、このID指定の差分で止まることがあります。
名前は似ていますが、追うべき識別子が変わる場面がある、という理解で十分です。
詳細はアップグレードガイドに沿って整理すると迷いません。

登録処理では、プロパティ名を画面表示のまま固定で持つより、まず対象データソースの定義を見て、どの項目がタイトル型か、どの項目が必須入力なのかを把握してから値を詰めるほうが安定します。
たとえば問い合わせ受付なら、タイトルに件名、日付に受付日、セレクトに受付経路、テキストに備考という並びです。
この「型に合わせて1件作る」感覚がつかめると、フォームやスプレッドシートからの投入にもそのままつながります。

💡 Tip

API経由で連続登録するときは、Notion側のレート制限も前提に入ります。平均で約3リクエスト/秒が目安とされ、429のときは Retry-After が秒数の整数で返ります。1件追記では目立ちませんが、複数件をまとめて流す設計では待機処理が要ります。

フォーム/スプレッドシート連携の型

自動化の入口として手応えが出やすいのは、Google フォームやGoogle スプレッドシートからNotionへ1件ずつ追記する流れです。
たとえばGoogle フォームで受けた問い合わせを『Google Sheets』に自動保存し、『Google Apps Script』の onFormSubmit でその1行を拾ってNotionへ登録する、という形です。
フォームからシートへの保存は標準機能でつながり、Apps Script では UrlFetchApp.fetchheaders を渡して外部APIを呼べます。
ここまでがそろうと、「入力はGoogle側、蓄積はNotion側」という役割分担が作れます。

設計の芯になるのは、入力値をそのまま送ることではなく、Notionの必須プロパティへ変換してから1件化することです。
フォームの質問文やシート列名をそのままNotionの列名に合わせる方法もありますが、実務では微妙にズレることが多いので、Apps Script 側でマッピングを1か所にまとめておくほうが扱いやすい形になります。
フォームの回答が増えても、送信ロジックはその表を見て組み立てれば済むからです。

最小フローに絞るなら、押さえる点は次の3つです。

  • 必須プロパティを先に決める

Notion側で必須になるタイトル列と、運用上ないと困る列を最初に固定します。
たとえば「件名」「受付日時」「送信元」の3つだけにしておくと、フォーム項目が増えても登録部分は崩れません。

  • 送信前にバリデーションを入れる

空文字のまま送るとタイトル未設定で失敗しやすく、日付列に不正な値を入れると型エラーになります。
Apps Script 側で「タイトルが空なら送らない」「日付が解釈できない行は保留にする」と決めておくと、失敗の原因が追えます。

  • 失敗時の再送設計を用意する

フォーム送信直後の一発勝負にせず、シートに「未送信 / 送信済み / エラー」の列を持たせると、再送対象だけ拾えます。
Notion API 側で429が返ったら Retry-After に従って待つ、Apps Script 側ではレスポンスコードと本文を残す、という2段構えにしておくと復旧が早くなります。

この連携では、リアルタイム性よりも「1件も落とさない」設計のほうが効きます。
Apps Script は外部API呼び出しに使えますが、トリガー実行には時間上限があるので、1件ごとの処理を短く保ち、詰まった行だけをあとで再送できる構造が合っています。
フォーム直送で毎回すぐNotionへ入れるより、いったんGoogle スプレッドシートに受けて記録を残し、そこから追記する形のほうが運用時に状況を見失いません。

たとえば社内の問い合わせ受付なら、Google フォームで入力された内容が『Google Sheets』に1行入り、その行を『Google Apps Script』で読み、Notionの問い合わせデータソースへタイトル・受付日・担当区分つきで1件追加する、という流れで十分に回り始めます。
ここまで組めると、API連携が単なるサンプルではなく、日々の入力を減らす実用品として見えてきます。

Google Sheets API Overview  |  Google for Developers developers.google.com

自動化でよく使う3パターン

定期同期

実務でまず形になりやすいのは、外部システムの情報を決まった時刻にNotionへ集める定期同期です。
たとえば毎朝、タスク管理ツールの未完了案件をまとめてNotionへ寄せる、週次で売上や問い合わせ件数を集約してレポート用データソースに追記する、といった使い方です。
入口が複数あっても、出口をNotionのダッシュボードやレポートにそろえると、見る場所が一つに定まります。
この「Notionを情報ハブにする」発想があると、自動化の目的が単なる転記ではなく、意思決定に使うための整流に変わります。

実装の軸は時間ベース実行です。
『Google Apps Script』なら時間主導トリガーで朝や週次に回せますし、サーバー側で組むなら cron でも同じ考え方で進められます。
ただし、毎回全件を取り直して全件を書き戻す構成だと、件数が増えた瞬間に無駄が目立ちます。
Notion APIは平均で約3リクエスト/秒のレート制限があり(com/reference/request-limits)で平均約3リクエスト/秒が目安のので、同期対象が増えるほど差分同期の設計が効いてきます。

差分同期の基本は単純で、前回同期時刻か外部側の更新日時を持ち、更新が入ったものだけを拾います。
さらにNotion側のページIDと外部レコードIDの対応表をどこかに保持しておくと、新規作成と更新の分岐が切れます。
私がよく取る形は、外部ID、最終同期時刻、同期結果を管理用のGoogle スプレッドシートかNotion内の別データソースに持たせる方法です。
こうしておくと、朝の同期で失敗した対象だけを拾い直せますし、週次レポートのように100件単位でページネーションを回す処理でも、毎回ゼロから舐め直さずに済みます。

出口から逆算してプロパティを決めるのも、このパターンでは効きます。
週次レポートを見たいなら「日付」「担当」「案件種別」「ステータス」のように集計軸になる列を先に固定し、その形に合わせて外部データを寄せます。
先に同期元の項目一覧から考え始めると、あとでNotion側のビューや集計が組みにくくなります。
運用が続くほど、入力元よりも出口の見え方が設計を支配します。

フォーム/スプレッドシート連携

入力の自動化で現場になじみやすいのは、Google フォームやGoogle スプレッドシートを入口にしてNotionへ流す型です。
前のセクションでは1件追記の基本を見ましたが、実務で安定するのは「入力」「検証」「登録」を分ける構成です。
Google フォームの回答は標準で『Google Sheets』へリアルタイム反映できるので、まずシートに受け、そのあと『Google Apps Script』の onFormSubmit や時間主導トリガーでNotionへ渡す流れにすると、途中経過が残ります。

ここで効くのが“保留シート”戦略です。
フォーム回答の中には、必須プロパティが空のものや、日付・セレクト値の変換に失敗するものが必ず混ざります。
そのたびにスクリプトを止めるのではなく、未充足やバリデーション失敗の行だけを別シートへ移します。
元シートには受信ログ、保留シートには修正待ちの行、送信済みシートには正常登録済みの行という役割分担にしておくと、どこで詰まったかが一目で追えます。

たとえば問い合わせ受付なら、フォーム上の質問文は人が答えやすい表現にして、Notion側ではタイトル、受付日、カテゴリ、担当候補のような運用列に変換します。
このとき、フォームの項目名とNotionのプロパティ名を無理に一致させる必要はありません。
むしろ、Apps Script 側に変換表を持たせて、「この回答列はタイトルへ」「この選択肢はカテゴリへ」と明示したほうが、フォーム文面の修正や運用変更に耐えます。
『Google Sheets』でも大量行を扱うときは一括取得・一括書き込みのほうが処理効率がよいので、保留判定も1行ずつよりまとめて処理したほうが流れが安定します。

リアルタイム転送にこだわらず、いったん『Google Sheets』に貯める設計が効く場面も多いです。
フォーム回答が多い日ほど、その場で全部を送るより、夜間にまとめて同期するほうが崩れません。
シートは受け皿として優秀で、Notionは整理された情報ハブとして強いので、役割を分けたほうが運用の負荷が下がります。
入力の自由度はGoogle側に持たせ、見る場所と集計はNotionに寄せる、という組み方です。

Webhook起点の更新

同期を時刻ではなくイベントで動かしたいときは、Webhook起点の更新が候補になります。
Notion側の変更をきっかけに外部処理を動かすなら、Integration Webhooksで通知を受け、受信エンドポイント側で再計算や別サービスへの反映を行う構成です。
たとえばNotionで案件ステータスが変わったら通知を受け、社内チャットに連携したり、外部の業務システムへ更新を返したりできます。
定期同期より即時性があり、フォーム連携よりも「Notionで起きた変化を外へ配る」発想に向いています。

この方式で先に決めておくと運用が安定するのが、リトライ時の冪等性です。
Webhook は再送が前提なので、同じイベントを二重に処理しない仕組みが欠かせません。
実際に運用していて扱いやすかったのは、受信したリクエストIDを保存し、既に処理済みのIDなら何もせず成功として返す形でした。
これを後回しにすると、通知の再送や一時障害のあとで同じページ更新が何度も走り、原因の切り分けに時間を取られます。

受信側では、署名検証、イベントの妥当性確認、失敗時の再送前提のレスポンス設計が土台になります。
Webhook の本体処理を長く持たせるより、受信したらまず検証して受け付け、重い再計算や外部反映は別ジョブへ渡すほうが詰まりません。
Notion API自体もバージョン更新が続いており、Versioning - Notion Docsを見ると 2026-03-11 が現行の節目として整理されています。
Webhook を将来広げる前提なら、APIバージョンとイベント処理の責務を分けておく設計のほうが保守しやすくなります。

Webhookは最初から全自動の完成形を狙うより、将来の発展先として位置づけると収まりがよいです。
定期同期でデータを集め、フォーム連携で入力経路を固め、そのあとNotion上の変更を起点に外へ波及させると、段階的に育てられます。
ZapierMakeYoomのような既存連携でも入口は作れますが、変更通知を受けて独自ルールで再計算する場面では、Webhook と受信エンドポイントを持つ構成が一段自由です。
情報の集約先をNotionに据え、そこから何を見せ、どこへ返すかを決めると、自動化の3パターンがばらばらな小技ではなく、一つの運用設計としてつながります。

Versioning - Notion Docs developers.notion.com

初心者がハマりやすい制限と対策

3req/sと429の扱い

Notion APIは、インテグレーションあたり平均で 3リクエスト/秒 を目安に設計する必要があります。
ここを雑に通すと、最初は動いていても、件数が増えた瞬間に不安定になります。
実際、私も最初は“とりあえずforループ”で一気に投げれば済むと思っていましたが、すぐに 429 を踏みました。
そこから Retry-After を尊重する形に直し、即時実行ではなくキュー処理へ切り替えただけで、失敗の出方がまるで変わりました。

429 が返ったときは、レスポンスヘッダーの Retry-After に入っている整数(秒)だけ待ってから再試行してください。
ここで短めに待つ独自判断をすると、再試行でも再び 429 になりやすく、全体の処理時間がかえって伸びます。
実務では、待機に加えてバースト抑制、処理の直列化、指数バックオフの併用による三段構えで設計することを推奨します。

実務では、単に「待つ」だけでは足りません。
まず一時的な集中送信を避けるためにバーストを抑え、次に更新系の処理を必要に応じて直列化し、失敗時には指数バックオフを入れる、という三段構えで考えると崩れにくくなります。
たとえばページ作成やプロパティ更新を何十件もまとめて流す場面では、同時に全部送るより、ワーカーが順に取り出すキューのほうが制御しやすく、429 が出たときも一時停止と再開の境界が明確です。

『Google Apps Script』で組む場合も同じで、外部 API 呼び出しをその場のループにべったり書くより、送信対象をシートやキュー相当の配列に積み、処理済み・待機中・失敗を分けて持つほうが扱いやすくなります。
Apps Script は1実行あたり約6分の上限があるので、レート制限に引っかかったまま長く粘る実装は相性がよくありません。
短く動いて状態を残し、次回トリガーで続きから流す構成のほうが、日次の実行時間制限にも収まりやすくなります。

100件上限とカーソル設計

一覧取得系で最初につまずきやすいのが、1回で取れる件数は最大100件 という前提です。
データベースやリストの取得で「100件しか返ってこない」と見えたら不具合ではなく仕様です。
ここで「たまたま100件しかない」と思い込んで実装すると、101件目が入った日から同期漏れが始まります。

ページネーションは has_morenext_cursor を使って回します。
1回目のレスポンスで has_moretrue なら、次回リクエストに start_cursor として next_cursor を渡し、has_morefalse になるまで続ける、という流れです。
ポイントは、カーソルをその場限りの変数で終わらせないことです。
途中でタイムアウトや実行終了が起きるなら、最後に成功した next_cursor を保存しておき、再開時にそこから続ける設計にしておくと取りこぼしを防げます。

実運用では、カーソルだけで押し切らない判断もよく使います。
件数が多いワークスペースでは、全件を毎回先頭からたどると、レート制限にも実行時間にも効いてきます。
そういうときは更新日時の期間で区切る、特定のキー範囲で分ける、担当者やカテゴリ単位でジョブを分割する、といった形にしたほうが現実的です。
夜間バッチで大量データを流す場面でも、1本の巨大ジョブより、日付やグループごとに分割した複数ジョブのほうが復旧しやすく、どこで止まったかも追えます。

next_cursor は便利ですが、万能ではありません。
大きな同期では「前回の続きを取る仕組み」と「どの単位で仕事を分けるか」の両方が必要です。
この2つを分けて考えると、件数が増えても構成を保ちやすくなります。

絞り込みと分割の実務Tips

大量データを扱うほど、まず効くのは全部取ってから考えるのをやめることです。
クエリで絞り込みを入れ、必要な並び順を先に決めておくと、応答サイズが減り、後段のメモリ消費やタイムアウトも抑えられます。
同期処理では「未処理だけ取る」「更新日が前回以降のものだけ取る」「ステータスが対象のものだけ取る」といった具体化が、そのまま安定性につながります。

たとえば問い合わせ台帳や案件管理のように更新が継続するデータでは、全件再取得より「前回同期時刻以降に更新された行だけ」を取りにいくほうが筋がよいです。
並び順も更新日時の昇順か降順かを先に固定しておけば、途中停止したときの再開位置を決めやすくなります。
結果として、デバッグ時に「どの範囲を取ったか」がログから読み取りやすくなります。

分割の切り方にも実務の癖があります。
期間分割はもっとも素直で、日次・週次・月次のように区切ると保守しやすくなります。
キー分割は、たとえば A-FG-M のようにタイトルやIDの範囲で担当を分ける考え方です。
どちらを使うにしても、1回のジョブが長くなりすぎないことが欠かせません。
『Google Apps Script』では1実行が約6分で打ち切られるため、100件ずつページネーションしても、変換や書き戻しまで含めると意外に時間を使います。
処理対象を小さく切っておくと、トリガー再実行との相性がよくなります。

💡 Tip

取得条件を具体化すると、APIの呼び出し回数だけでなく、レスポンスを展開したあとの配列処理やシート書き込みも軽くなります。同期の不安定さは通信だけで起きるわけではなく、後段の整形処理で詰まることも多いからです。

『Google Sheets』を中継に使うなら、この分割戦略はさらに効きます。
夜間バッチで一括同期する場合でも、全行を毎回なめるより、未送信フラグ付きの行だけをまとめて拾い、一括で送って結果だけ書き戻す構成のほうが流れが明快です。
シート側も getValues()setValues() のまとまった操作に寄せると、行ごとの読み書きより処理量を抑えられます。

キー管理のベストプラクティス

ローテーション手順も先に決めておくと詰まりません。
現場では「漏えいしたら差し替える」では遅く、少なくとも「新しいトークンを発行する」「実行環境の環境変数を差し替える」「疎通確認後に旧トークンを失効する」という順番を作っておくと、切り替え時の停止時間を短くできます。
Notionの開発者向け管理画面や。

CI/CD まわりでは、Git履歴に混入させない対策も欠かせません。
具体的には、.env をリポジトリに含めない設定、シークレット値をログへ出さないマスキング、プルリク前のシークレットスキャンを入れておく、といった守り方です。
うっかり1回コミットしただけでも、履歴に残ると回収が面倒になります。
ローカルで試したトークンをそのまま貼って push してしまう事故は珍しくありません。

『Google Apps Script』でも同様で、スクリプト本文にベタ書きするより、少なくとも設定値を分離し、編集権限を必要最小限に絞るほうが安全です。
トークンは「動かすための文字列」ではなく、ワークスペース内の情報へ触れる鍵です。
この感覚を最初から持っておくと、あとで公開範囲や権限を広げるときに慌てずに済みます。

APIバージョン変更への備え

Notion-Versionの固定

Notion APIを運用に載せるなら、Notion-Version は毎回ヘッダーで明示的に固定しておく前提で組むほうが安全です。
あわせて、クライアントライブラリの版もセットで管理します。
たとえば@notionhq/clientは、どの API バージョンを前提にしているかで使えるフィールドやメソッドの挙動が変わります。
バージョン指定をコード側だけでなく、運用ドキュメントやデプロイ設定にも残しておくと、後から見返したときに「この実装が何を前提にしていたのか」がぶれません。

JavaScript や『Google Apps Script』から直接 REST を叩く場合は、Authorization と同じくらい Notion-Version の指定が効いてきます。
『UrlFetchApp』でヘッダーを渡せるので、トークンだけでなく版も常に送る構成にしておくと、実行環境が増えても挙動をそろえられます。
SDK を使う場合も安心し切らず、実際に採用している版が対象 API をサポートしているかを合わせて見ます。
Notionのバージョニング情報では、@notionhq/client v5.12.0 が 2026-03-11 をサポートしています。

筆者は以前、この固定を後回しにしていた時期がありました。
小さな検証では動いていたので油断していたのですが、ある日想定外の破壊的変更を踏み、休日に復旧対応をすることになりました。
そのとき痛感したのは、バージョンピンは保守の最適化ではなく、最初から入れておくべき前提条件だということです。
動いている間は地味に見えても、止まった瞬間に効いてきます。

2026-03-11の破壊的変更

2026-03-11 では、既存コードに影響しうる破壊的変更が3つ入っています。
ひとつは block 追加時の append block children における position 指定です。
これまで末尾追加前提で組んでいた処理でも、途中挿入や順序制御をしているコードは見直しが必要になります。
ページ本文をテンプレート的に組み立てている処理ほど、この差分が露出しやすくなります。

もうひとつは、archivedin_trash へ置き換わる点です。
削除状態の判定や復元フローで archived を前提にしていると、検索条件や更新処理がそのままでは噛み合いません。
ゴミ箱相当の扱いを自前ロジックで包んでいた実装ほど、名前の変更がそのまま条件漏れにつながります。

名称変更では、transcriptionmeeting_notes に変わっています。
AI 系や会議記録まわりのプロパティ名を文字列で直接見ているコードは、型で守られていないぶん影響を受けやすいのが利点です。
とくに Webhook の受け側やログ集計では、見た目は些細でも集計軸がずれて気づきにくいので厄介です。

こうした変更は、「エラーになってすぐ止まる」ケースより、「一部だけ静かに意味が変わる」ケースのほうが復旧に時間を取られます。
レスポンスのキー名をそのまま業務ロジックに流し込まず、アプリ内部の名前へ一段マッピングしておくと、将来の差し替え範囲を狭く保てます。

2025-09-03の移行ポイント

2025-09-03 の更新では、multi-source databases への対応に伴って、設計の前提がひとつ変わりました。
一部の操作で、これまでの database_id ベースの考え方だけでは足りず、data_source_id への移行が必要になります。
古い記事では「データベース ID を渡せばよい」と書かれていることが多いのですが、この版以降はその説明だけでは不足します。

影響が出やすいのは、データ取得や作成を「データベース」という単位で一括りにしていた実装です。
UI 上では同じように見えても、API では data source を意識しないと辻褄が合わない場面が出てきます。
既存コードに database_id という名前の変数が広く残っていると、どこまでがそのまま使えて、どこから置き換え対象なのかの切り分けに手間がかかります。
ここは単純な検索置換では済まず、エンドポイント単位で確認したほうが事故が少なくなります。

💡 Tip

database_iddata_source_id が混在する時期は、変数名とログ項目を分けておくと追跡が楽になります。識別子の種類が曖昧なまま id だけで持つと、検証時にどのレイヤーで食い違っているのか読めなくなります。

この版は非後方互換の変更を含むため、検索上位の解説をそのまま信じるより、Notionの Upgrade Guide を基準に差分を追ったほうが確実です。
『Versioning』 と関連する移行ガイドを見ると、SDK が吸収してくれる部分と、アプリ側の修正が要る部分の境界が見えます。
特に@notionhq/clientを使っていても、アプリの変数名、保存している識別子、テストデータの前提までは自動で直りません。

変更検知と検証フロー

運用では、新バージョンが出たらすぐ本番の Notion-Version を切り替えるのではなく、ステージングで検証してから本番を更新する流れを固定すると安定します。
見るべき点は広くありません。
ページ取得、データ追加、更新、アーカイブ相当の状態変更、ページネーションの継続取得という、実際に使っている経路を少数の代表ケースで通せば、破壊的変更の影響はだいたい見えます。

変更検知はNotionの Changelog 監視を軸にすると取りこぼしが減ります。
Changelog に目を通す運用を入れておくと、用語変更や廃止予定を先に拾えます。
ここで効くのは、API レベルの結合テストを「通るかどうか」だけでなく、「返ってくるキーが想定どおりか」「削除状態の意味づけが変わっていないか」まで見ることです。
JSON のスナップショット比較を入れておくと、名前変更のような差分を早めに見つけられます。

『Google Apps Script』運用なら、ステージング用のトークンと本番用のトークンを分け、トリガーも別にしておくと切り戻しが軽くなります。
時間主導トリガーは1実行が約6分で打ち切られるので、検証ジョブも本番同様に短く分割しておいたほうが、実戦に近い形で挙動を確認できます。
Notion 側の平均 3 リクエスト/秒という目安や 429 の Retry-After も、検証時点でログに出しておくと、版更新と単なる負荷超過を切り分けやすくなります。

実務では、壊れたあとに原因を探すより、差分が出た瞬間に止める仕組みのほうが効きます。
Notion-Version、SDK 版、対象エンドポイント、主要レスポンスの形を1セットで記録しておくと、「どの変更で崩れたのか」を短時間で特定できます。
API の保守は実装力だけでなく、更新を受け止める手順の有無で安定度が変わります。

よくあるエラーと対処法

権限不足(共有漏れ)

認証情報もエンドポイントも合っているのに失敗するとき、まず疑うべきなのは権限不足です。
Notionでは、インテグレーションを作成しただけでは対象ページやデータソースに触れられません。
該当のページ、またはデータソース側でそのインテグレーションが共有先に追加されていないと、読めるはずの情報が取れなかったり、作成や更新が弾かれたりします。

実務では、一見「トークンは正しい」ように見えるのに失敗する場面の大半が、この共有漏れでした。
コード側でヘッダーやIDを何度見直しても進まないときほど、対象側の共有欄を開いて、インテグレーション名が本当に追加されているかを見るほうが早く片付きます。
ページを複製した、親ページだけ共有したつもりで子ページは未共有だった、データベースは共有したが参照先の個別ページは別扱いだった、といった食い違いもよく起きます。

対処は単純で、エラーが出ている対象ページまたは対象データソースを開き、共有から該当インテグレーションを追加して再試行します。
404 やオブジェクト未検出に見える場合でも、実際には「見えていないだけ」ということがあります。
あわせて、参照している ID が別ワークスペースのものではないか、すでに in_trash 状態のオブジェクトを掴んでいないかも切り分け対象です。
見つからないエラーは存在しないとは限らず、「そのトークンから見えない」だけのことが少なくありません。

認証・トークンの問題

認証エラーは、トークン未設定、古い値のままデプロイしている、無効化済みトークンを使っている、といった初歩的な原因から起こります。
『Google Apps Script』ならスクリプトプロパティや環境変数相当の保管場所、Node.jsなら .env や実行環境のシークレット設定に、想定した値が入っているかを最初に見ます。
ハードコードしたトークンをどこかで書き換えて、別環境だけ古いまま残る事故もありがちです。

トークン漏えいが疑われるときは、動作確認より先にローテーションです。
Notionのインテグレーション管理で新しいシークレットに切り替え、実行環境の環境変数も更新し、古いトークンは使えない状態にします。
そのうえで、最初に広く付けてしまった権限があるなら不要なものを削り、読み取りだけで足りる処理に書き込み権限を残さない構成へ寄せたほうが後の事故を小さくできます。

認証まわりでは、ヘッダー形式の崩れも見落としがちです。
Authorization: Bearer ... の空白抜けや、別サービス用のトークンを流用しているケースは、ログを見れば単純なのに、見ないままハマると時間を使います。
『Google Apps Script』の UrlFetchApp.fetch はヘッダー指定ができるので、失敗時は muteHttpExceptions: true でレスポンス本文を取り、HTTP ステータスとエラーメッセージを残しておくと、認証失敗とバリデーション失敗の混同を避けられます。
『Google』の『UrlFetchApp』リファレンスでも、その挙動が確認できます。

429の再試行設計

429 rate_limited は、壊れているのではなく、短時間に投げすぎている合図です。
Notionのリクエスト制限は平均で毎秒3リクエストが目安なので、フォーム送信をそのまま並列に流したり、一覧取得の後にページ更新を一気に重ねたりすると、すぐ上限に触れます。
429 が返ったら、その場で連打せず、レスポンスの Retry-After に入っている秒数だけ待つのが先です。
その後に指数バックオフを入れると、混雑が続いている状況でもリトライが雪崩れません。

実装では、再試行間隔を少しずつ伸ばすだけでなく、そもそも同時発行数を抑える設計が効きます。
たとえば『Google Apps Script』でフォーム送信ごとに即時 API を叩く形は、回答が重なった瞬間に負荷が跳ねます。
シートに一度ためて時間主導トリガーで順番に処理する、キューを介して直列化する、1件ずつ送るワーカーを分ける、といった構成のほうが安定します。
バースト時ほど「速く送る」より「詰まらせない」ことのほうが効きます。

⚠️ Warning

429 は単なる一時エラーではなく「短時間に投げすぎている」合図です。再試行コードだけで片づけず、送信の直列化やキューイングまで含めて設計することをおすすめします。

ページネーションを伴う処理でも同じです。
1回の取得で抱え込みすぎず、取得、整形、書き込みを小分けにしたほうが、再開位置を持ちやすくなります。
429 では Retry-After に従う前提が明確です。

validation_errorの切り分け

validation_error は、送っている JSON の形がNotion側の期待と合っていないときに出ます。
典型例は、プロパティ名の打ち間違い、title に文字列を直接入れてしまうような型違い、number に文字列を渡しているケースです。
データベースやデータソースの列名を見た目で覚えてコードに写すと、全角半角の違い、末尾スペース、UI での表示名変更に引っかかります。

ここで近道になるのは、コードを眺め続けることではなく、対象スキーマをNotionの UI で見直すことです。
今あるプロパティ名が何か、必須扱いの列があるか、選択肢が固定の select か、日付型か、リレーションかを確認し、その型に合う値だけを組み立てます。
エラー本文に対象プロパティが出ることも多いので、レスポンスを丸ごと捨てずに保存しておくと切り分けが速くなります。

バージョン更新の影響で、過去に通っていたリクエストが validation_error に変わることもあります。
前のセクションで触れた database_iddata_source_id の混同もその一つです。
ID の種類が違うのに同じ id 変数へ押し込んでいると、見た目は JSON が整っていても、送信先の前提がずれて失敗します。
エラーメッセージが曖昧なときは、まずリクエストボディを最小構成に削り、必須プロパティだけで通るかを試すと、どの項目で崩れているかが見えます。

404 やオブジェクト未検出と迷う場面では、順番に切り分けると混乱しません。
ID の取り違え、ワークスペース相違、ゴミ箱に入ったオブジェクト参照を先に潰し、そのあとでスキーマ不一致を見る流れだと、原因が混線しにくくなります。
validation_error は「送信内容の形」に寄ったエラーなので、認証や共有漏れの問題と一緒に扱わないほうが復旧は早まります。

まとめと次のアクション

要点の総括

Notion API導入で最初に効くのは、トークン発行よりも対象ページやデータソースへの共有を終えることです。
そこが通ると、最小サンプルで「読めた」「1件書けた」という成功体験が作れます。
運用段階では、レート制御と API バージョン固定を先に仕込んでおくと、あとで崩れません。

次のアクション

次は、内部インテグレーションを 1 つ作り、テスト用ページに接続してください。
そのうえでJavaScriptまたは『Google Apps Script』で読み取りを 1 回成功させ、続けてフォームやシートからNotionへ 1 件だけ追加してみる流れが最短です。
現場では、まず 1 件通してから制限対応や保守を足す順で進めたほうが導入が早く、失敗時の切り戻しも整理しやすくなりました。
実運用へ上げる前に、429 対応、ページネーション、API バージョン固定まで入れておくと、後続の修正が局所化できます。

この記事をシェア

A
AIビルダー編集部

AIビルダーの編集チームです。AI開発ツールの最新情報と使い方を発信しています。

関連記事

Notion

Notion カスタムエージェント 使い方・Slack連携・料金

Notion

Notion カスタムエージェントの始め方を初心者向けに解説。Agents からの作成手順、トリガー/アクセス権の設計、Slack連携の管理者要件、料金とクレジット節約のコツ、ログでの改善まで。

Notion

Notion タスク管理の始め方|テンプレと自作設定

Notion

Notionでタスク管理を始めたいのに、テンプレートが多すぎて止まったり、自作しようとしてプロパティ設計で手が止まったりする人は少なくありません。この記事は、まず1つのタスクデータベースを軸に、ステータス・期限・優先度・担当・関連プロジェクトだけを置いた形まで、迷わず再現できる手順を案内します。

Notion

Notion 料金プラン比較【無料/Plus/Business】

Notion

『Notion』の料金プランは、個人のノート管理ならFree、2〜数人で画像やPDFも扱う小規模チームならPlus、AIを業務に組み込むチームならBusinessを起点に考えると、選択をほぼ間違えません。

Notion

Notion リレーションとロールアップの違いと設定

Notion

『Notion』でリレーションとロールアップが腹落ちしないうちは、タスク管理も案件管理もどこかで詰まります。私自身、最初は Select で「プロジェクト名」を持たせて運用し、表記ゆれで集計が崩れてから、ようやく Relation に切り替えて整理できました。