Notion APIの使い方:インテグレーションを作って外からデータを読み書きする

公開 書いた人 大林 諒

Notion APIは、Notionの外のプログラムからページやデータベースを読み書きするための入口です。ホームページのフォームに入った申込をNotionのデータベースに自動で入れる、Notionの一覧を毎朝別のシステムに写す、といった作業を人の手を介さずに回せます。この記事では、APIでできることとできないこと、内部インテグレーション(今の画面では「コネクション」)を作ってページに接続する手順、curlで1行足して読むまでの実際のリクエスト、上限とページ送り、今のAPIでのデータベースとデータソースの扱い、トークンの管理までを順に書きます。読み終わると、自社のデータベースに外から1行足す仕組みを自分で試せます。前提は、ワークスペースのオーナーであることと、ターミナルでcurlを打てることです。コードを書かずに組みたい場合は、最後に挙げるn8nの記事のほうが早道です。

例は、このコラムで通して使っている、スタッフ6名ほどの予約制の小さな料理教室「サンプル料理教室」です。ホームページ sample-cooking.example の体験申込フォームに入った申込を、Notionの「体験申込」データベースに1行ずつ入れます。

サンプル料理教室の「体験申込」データベース。名前・申込日・申込経路・体験日・入会したかの列が並んでいるところ
サンプル料理教室の「体験申込」データベース。名前・申込日・申込経路・体験日・入会したかの列が並んでいるところ
この記事の内容
  1. 先に決めること
  2. APIでできること・できないこと
  3. 内部インテグレーションを作る
  4. curlでデータベースに1行足す・読む
  5. リクエストの上限とページ送り
  6. データベースとデータソース
  7. トークンの管理
  8. 応用
  9. よく詰まるところ
  10. 次にやること

先に決めること

APIは、つないでしまえば何でもできそうに見えます。ただ、決めずに始めると、トークンが誰のものか分からない、権限が広すぎて全部のページを読める、上限に引っかかって夜中に止まる、が起きます。触る前に次の5つを決めます。

1つ目は、本当にAPIが要るかです。データベースの変化を外に知らせるだけなら、オートメーションの「Webhookを送信」で足ります。フォームの回答をためるだけならNotionのフォームで足ります。外からNotionに書き込みたい、Notionの中身を定期的に読みたい、という向きのときにAPIを使います。

2つ目は、自分で書くか、n8nのような道具を使うかです。サンプル料理教室では、ホームページからの申込はn8nを通して入れています。n8nのNotionのノードも中ではこのAPIを使っているので、この記事の話はn8nで組むときにもそのまま効きます。

3つ目は、認証の方式です。Notion APIのトークンには、ワークスペースに結びつく内部コネクション、作った本人として動くパーソナルアクセストークン、OAuthで他社のワークスペースにも入れる公開コネクションの3つがあります。自社の自動化なら内部コネクションを使います。作った人が辞めても止まらないからです。

4つ目は、どのページに接続するかです。コネクションは、接続を許可したページとその中のデータベースにしか触れません。サンプル料理教室では「体験申込」データベースだけに接続し、代表の経営メモや採用には接続しません。

5つ目は、トークンを誰が持つかです。トークンはパスワードと同じです。代表が作り、パスワードの管理ツールに入れ、使う仕組みには環境変数として渡す、と決めておきます。

APIでできること・できないこと

対象できること主なエンドポイント
ページ作る・読む・プロパティを変える・移す・ゴミ箱に入れる・本文をMarkdownで読み書きするPOST /v1/pages、PATCH /v1/pages/{id}
データベース作る・読む・タイトルなどを変えるGET /v1/databases/{id}
データソースプロパティの定義を読む・変える・中の行を条件で絞って取り出すPOST /v1/data_sources/{id}/query
ブロックページの本文のブロックを読む・足す・変える・消すPATCH /v1/blocks/{id}/children
ユーザーワークスペースのユーザーの一覧と情報を読むGET /v1/users
コメントコメントを読む・付ける・変える・消すPOST /v1/comments
検索接続されたページとデータソースをタイトルで探すPOST /v1/search
ファイルファイルをアップロードしてページやプロパティに付けるFile uploads の各エンドポイント
ビュービューを作る・読む・変える・消すViews の各エンドポイント

できないことも先に並べます。

できないことどうなるか・代わりの手
数式・ロールアップ・ID・作成日時・作成者・最終編集日時・最終編集者・ボタンのプロパティに値を入れるAPIからは設定できない。Notionが自動で計算・記録するので、書き込む中身からは外す
接続していないページを読むエラーになる。読ませたいページにコネクションを追加する
ボタンやオートメーションを作る確かめたAPIリファレンスの一覧にエンドポイントが無い。Notionの画面で作る
メンバーを招待する・ページの共有相手を変えるユーザーのAPIは一覧と読み取りだけ。招待と共有は画面で行う
1回で大量の行をまとめて入れる1回のリクエストで作れるページは1つ。上限を守って1件ずつ送る

Notionのデータベースは、APIで書き込める台帳としては扱いやすい一方、毎日数万行が増える記録や、細かい承認の流れが要る台帳には向きません。そういう台帳はkintoneや本格的なデータベースに置き、Notionには人が読む分だけを写す分け方が長続きします。

内部インテグレーションを作る

ここからが手順です。「設定」の「コネクト」でコネクションを作り、トークンを取り出し、ページに接続します。

コネクションを作ってトークンを取り出す

  1. ワークスペースのオーナーのアカウントで、サイドバーの「設定」を開き、「コネクト」のタブを選ぶ
  2. 「+ 新規コネクション」を押し、名前を「ホームページ申込の取り込み」、ワークスペースをサンプル料理教室にして、ワークスペースの中だけで使う内部のコネクションとして作る
  3. 「コネクト権限の詳細設定」で、内容の読み取りと内容の挿入だけを残して保存する
  4. 「内部接続トークン」(開発者向けドキュメントでは Configuration タブの API トークン)をコピーする
  5. コピーしたトークンを、パスワードの管理ツールに「Notion ホームページ申込の取り込み」という名前で保存する
「設定」→「コネクト」で作ったコネクションの、内部接続トークンの欄が見えるところ
「設定」→「コネクト」で作ったコネクションの、内部接続トークンの欄が見えるところ

手順3で絞るのは、トークンが漏れたときの被害を小さくするためです。申込を入れるだけの仕組みには、内容の更新もコメントもユーザーの情報も要りません。行を足すには内容の挿入、行を読むには内容の読み取りの権限が要ります。足りないと403のエラーが返ります。

内部コネクションを作れるのはワークスペースのオーナーだけです。メンバーのアカウントでは作れないので、代表が作ります。

ページにコネクションを接続する

コネクションは作っただけでは何も読めません。触らせたいページに接続します。

  1. Notionで「体験申込」データベースのページを開く
  2. 右上の「•••」を押し、「その他」を選ぶ
  3. 「コネクション」の欄の「コネクションを追加」を選ぶ
  4. 「ホームページ申込の取り込み」を探して選ぶ
  5. 確認の画面が出たら許可する
体験申込データベースの「•••」メニューで「コネクションを追加」を開き、コネクションを選んでいるところ
体験申込データベースの「•••」メニューで「コネクションを追加」を開き、コネクションを選んでいるところ

外したいときは、同じ「•••」のメニューでコネクションの名前にカーソルを合わせ、「接続を解除」を押します。ワークスペース全体でどのコネクションが入っているかは「設定」→「コネクト」で見られます。

実際に運用していると、チームスペースのいちばん上のページに接続してしまい、その下の議事録や採用まで読める状態になっていることがあります。接続するのは、触らせたいデータベースそのものだけにします。

curlでデータベースに1行足す・読む

ここからはターミナルで試します。トークンは毎回打たずに環境変数に入れます。

export NOTION_API_KEY=ntn_xxxxxxxxxxxx

すべてのリクエストに、トークンを入れた Authorization、APIのバージョンを入れた Notion-Version、本文がある場合の Content-Type の3つのヘッダーを付けます。この記事では2026年10月時点のリファレンスの例と同じ 2026-03-11 を使います。

データソースのIDを調べる

今のAPIでは、行を足す先はデータベースではなくデータソースです(後で詳しく書きます)。まずデータベースのIDから、中にあるデータソースのIDを調べます。データベースのIDは、データベースをページとして開いたときのURLの、ワークスペース名の後ろの32文字です。

curl https://api.notion.com/v1/databases/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
  -H "Authorization: Bearer $NOTION_API_KEY" \
  -H "Notion-Version: 2026-03-11"

返ってきたJSONの data_sources の中の id が、データソースのIDです。Notionの画面では、データベースの設定のメニューにある「Manage data sources」から「Copy data source ID」でコピーすることもできます(開発者ドキュメントの英語表記)。

curlで体験申込のデータベースを読み、返ってきたJSONの data_sources の id が見えるターミナル
curlで体験申込のデータベースを読み、返ってきたJSONの data_sources の id が見えるターミナル

1行足す

ホームページから届いた申込を1件、体験申込に足します。プロパティの名前は、Notionの画面の列の名前と1文字も違わずに揃えます。

curl -X POST https://api.notion.com/v1/pages \
  -H "Authorization: Bearer $NOTION_API_KEY" \
  -H "Notion-Version: 2026-03-11" \
  -H "Content-Type: application/json" \
  -d '{
    "parent": { "type": "data_source_id", "data_source_id": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy" },
    "properties": {
      "名前": { "title": [ { "text": { "content": "体験 花子" } } ] },
      "申込日": { "date": { "start": "2026-10-31" } },
      "申込経路": { "select": { "name": "ホームページ" } },
      "体験日": { "date": { "start": "2026-11-08T10:00:00+09:00" } },
      "メール": { "email": "hanako@example.com" },
      "入会したか": { "checkbox": false }
    }
  }'

プロパティの種類ごとに書き方が違います。タイトルとテキストは文字の配列、日付は start(終わりがあれば end)、セレクトは選択肢の name、チェックボックスは true か false、メール・電話・URL・数値は値をそのまま入れます。セレクトの選択肢の名前は、送る側で固定します。実際に運用していると、送る側の表記ゆれで「HP」と「ホームページ」が並ぶことが起きます。

curlで足した「体験 花子」の行が体験申込データベースに入っているところ
curlで足した「体験 花子」の行が体験申込データベースに入っているところ

条件で絞って読む

申込経路がホームページで、入会していない行を、申込日の新しい順に読みます。

curl -X POST https://api.notion.com/v1/data_sources/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy/query \
  -H "Authorization: Bearer $NOTION_API_KEY" \
  -H "Notion-Version: 2026-03-11" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "and": [
        { "property": "申込経路", "select": { "equals": "ホームページ" } },
        { "property": "入会したか", "checkbox": { "equals": false } }
      ]
    },
    "sorts": [ { "property": "申込日", "direction": "descending" } ],
    "page_size": 100
  }'

返ってくるJSONの results に行が並び、has_more が true なら続きがあります。

クエリの結果のJSONで、results の行と has_more・next_cursor が見えるターミナル
クエリの結果のJSONで、results の行と has_more・next_cursor が見えるターミナル

ホームページから呼ぶときの形

ホームページのフォームから直接このリクエストを送るときは、必ずサーバー側で送ります。ブラウザで動くJavaScriptにトークンを書くと、ページのソースを見た人に丸見えになります。サーバー側のJavaScript(Node.js)なら次の形です。

const res = await fetch("https://api.notion.com/v1/pages", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.NOTION_API_KEY}`,
    "Notion-Version": "2026-03-11",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    parent: { type: "data_source_id", data_source_id: process.env.NOTION_DATA_SOURCE_ID },
    properties: {
      "名前": { title: [{ text: { content: form.name } }] },
      "申込日": { date: { start: new Date().toISOString().slice(0, 10) } },
      "申込経路": { select: { name: "ホームページ" } },
      "メール": { email: form.email },
    },
  }),
});
if (!res.ok) console.error(res.status, await res.text());

失敗したときに申込が消えないよう、Notionに送る前に申込をメールやサイト側にも残しておきます。サンプル料理教室では、フォームの送信でまずお客様への確認メールと受付へのメールを送り、そのあとNotionに入れる順にしています。Notionが止まっていても、申込はメールに残ります。

リクエストの上限とページ送り

APIには上限があり、超えると止まります。2026年10月時点のリファレンスの値です。

上限値超えたとき
コネクションごとのリクエストビジネス・エンタープライズは1分600回(平均1秒10回)、それ以外は1分180回(平均1秒3回)HTTP 429、rate_limited
データソースのクエリコネクションごとに約1秒3回の別枠HTTP 429
ワークスペース全体同じワークスペースの全コネクションで共有する上限があるHTTP 429
1回で返る件数page_size の最大は100続きは次のページで取る
テキストの長さ1つのテキストの中身は2000文字までHTTP 400、validation_error
メール・電話200文字までHTTP 400
マルチセレクト・リレーション・ユーザー1回で100件までHTTP 400
1回の送信の大きさ1000ブロック、500KBまでHTTP 400

429が返ったら、レスポンスの Retry-After ヘッダーの秒数だけ待ってから送り直します。待つ時間を少しずつ延ばし、送り直す回数にも上限を付けます。リファレンスでは、public_api_request_blocked が理由の429は送り直さないこととされています。

一覧を返すエンドポイントは、1回で全部は返しません。has_more が true なら、返ってきた next_cursor を次のリクエストの start_cursor に入れて続きを取ります。

curl -X POST https://api.notion.com/v1/data_sources/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy/query \
  -H "Authorization: Bearer $NOTION_API_KEY" \
  -H "Notion-Version: 2026-03-11" \
  -H "Content-Type: application/json" \
  -d '{ "page_size": 100, "start_cursor": "前のレスポンスのnext_cursorの値" }'

データソースのクエリは、結果が1万件で打ち切られることがあり、そのときはレスポンスの request_status が incomplete になります。1万件を超えるデータベースは、条件で絞って小分けに読みます。

もう1つ、書き込みで503が返ったときは注意が要ります。リファレンスには、変更は保存されたのに返事が間に合わず503になることがあると書かれています。そのまま送り直すと同じ申込が2行になります。送り直す前に、レスポンスに入っている保存済みのIDを読み直すか、メールアドレスと申込日で既にあるかを確かめます。

データベースとデータソース

2025年9月の版(2025-09-03)から、APIの中で「データベース」と「データソース」が分かれました。1つのデータベースに複数のデータソースをつなげられるようになったためです。これまでどおり1つの表しかないデータベースでも、中にデータソースが1つある、という形になります。

やりたいこと今のAPIでの宛先
行を足すparent に data_source_id を指定して POST /v1/pages
行を条件で取り出すPOST /v1/data_sources/{id}/query
列(プロパティ)を足す・変えるPATCH /v1/data_sources/{id}
データベースの名前・アイコン・置き場所を変えるPATCH /v1/databases/{id}
検索で表を探す検索の filter で object に data_source を指定

古いバージョンのまま作った仕組みは、誰かがそのデータベースにデータソースを足した瞬間に、ページの作成・読み取り・クエリが失敗するようになります。検索の結果からもそのデータベースが消えます。ネットの古い記事の例は database_id を親にして POST /v1/databases/{id}/query を呼ぶ形が多いので、そのまま写さないことです。

2026年3月の版(2026-03-11)では、ゴミ箱に入っているかどうかを表す archived が in_trash に、ブロックを足す位置を指定する after が position に変わりました。古いコードを今の版に上げるときは、この2つも直します。

オートメーションの「Webhookを送信」にも送る形式の版があり、新しく作った動作は 2026-03-11 が既定になります。

トークンの管理

トークンは、持っている人が接続したページを読み書きできる鍵です。

  1. トークンは人に送らない。チャットやメールに貼らない。共有が要るときはパスワードの管理ツールの共有の機能を使う
  2. コードやGitの中に書かない。環境変数やサーバーの秘密の設定から読む
  3. コネクションのできることは、使う分だけに絞る(申込を入れるだけなら挿入と読み取り)
  4. 接続するページは、使うデータベースだけにする
  5. 仕組みごとに別のコネクションを作る。1つのトークンを使い回さない
  6. 漏れたかもしれないと思ったら、すぐにそのコネクションを削除して作り直し、新しいトークンに差し替える
「設定」→「コネクト」の一覧で、「ホームページ申込の取り込み」の「•••」メニューを開いたところ
「設定」→「コネクト」の一覧で、「ホームページ申込の取り込み」の「•••」メニューを開いたところ

手順5は、漏れたときに止める範囲を小さくするためです。申込の取り込みとシフトの書き出しを1つのトークンで回していると、作り直したときに両方止まります。

パーソナルアクセストークンは、作った本人の権限で動き、その人が見られるページ全部に届きます。ビジネスとエンタープライズのプランでは作成が初期状態で制限されています。会社の仕組みには使わず、内部コネクションを使います。

応用

本文をMarkdownで書く

今のAPIでは、ページを作るときに markdown で本文を渡せます。体験申込の行の本文に、フォームの「ご質問」欄をそのまま入れておくと、受付がページを開いたときに読めます。markdown と children は同時には使えません。

送ったことで通知を鳴らさない

ページを作るときに notifications の mode を silent にすると、ページの更新・メンション・人物のプロパティへの追加の通知を止められます。古い申込をまとめて入れ直すときに、受付の通知が何十件も鳴るのを防げます。オートメーションやWebhookはそのまま動きます。

無料のワークスペースのブロックの上限

フリーのワークスペースには、2026年9月8日からブロックの上限が適用されています。具体的な上限は開発者ドキュメントの「Workspace block limits」のページにまとめられています。無料のまま大量に書き込む仕組みを作るときは、このページを先に読みます。

よく詰まるところ

症状原因直し方
401 unauthorizedトークンが違う、コピーで欠けたトークンを貼り直す。前後の空白を消す
404 で見つからないと言われるページにコネクションを接続していないデータベースの「•••」から「コネクションを追加」
403 が返るコネクションに挿入や読み取りの権限が無い「コネクト権限の詳細設定」で足す
400 validation_errorプロパティの名前が画面と違う、種類に合わない書き方データソースを読んで、名前と種類を確かめる
セレクトに似た選択肢が並ぶ送る側の表記がばらばら送る側で選択肢の名前を固定する
数式の列に書き込めない数式・ロールアップ・作成日時などはAPIから設定できない書き込みの中身から外す
昨日まで動いていたクエリが失敗する古いバージョンのまま、データベースにデータソースが足された2025-09-03 以降に上げ、データソースのIDで読む
夜中にまとめて流すと止まる1秒あたりの上限を超えた送る間隔を空け、429なら Retry-After だけ待つ
100件しか取れないページ送りをしていないnext_cursor を start_cursor に入れて続きを取る
同じ申込が2行入った503のあとに送り直した送り直す前に既にあるかを確かめる
日付が1日ずれる時刻付きの日付をタイムゾーン無しで送った+09:00 を付けるか、日付だけで送る
メンバーのアカウントでコネクションが作れない内部コネクションを作れるのはオーナーだけオーナーが作る

次にやること

コードを書かずに同じことをするなら、「Notionをn8nとつなぐ:フォーム・メール・kintoneから自動で登録する」が近道です。n8nのNotionのノードは、この記事の上限やデータソースの扱いの上で動いているので、詰まったときにこの記事の表が役に立ちます。

Notionの中の変化を外に知らせる向きは、APIより「Notionのボタンとオートメーション:繰り返しの作業を自動にする」の「Webhookを送信」のほうが手軽です。

最初に手元のExcelやスプレッドシートの台帳をまとめて入れたいなら、APIで1行ずつ送るより「NotionのCSVインポートとエクスポート:Excelやスプレッドシートから移す」のほうが早く終わります。SlackやTeamsへの通知は「NotionとSlack・Googleカレンダー・Teamsの連携:通知と予定をつなぐ」にまとめています。

あわせて読む