【Notion】APIついに実装...!簡単な動作確認をやってみる...!

どーも!

たかぽんです!

とうとうNotionでAPIの機能が実装されましたね...!

API機能実装が影響してかわかりませんが、昨日は一日アクセスできない不具合も発生していました...w

(がんばれNotion運営の皆様...!)

今回はAPI機能を実際に試してみようかなぁと思います!

NotionのAPIって?

さて、まず最初にお伝えしておきたいのですが、APIは主に開発者の人が使う機能になっています。

そのため、一般的なユースケースではほとんど活用できない...ということはあらかじめお伝えしておきます。

例えばですが...

自分で作っているアプリやサービスから直接Notionの一部の記事情報を閲覧したり、特定のデータベースへデータを入れたり...といったことができます。

逆に、普段使いでNotionを使っている人からすると、Notionのシステムの一機能として新しく便利なことができるようになった...!といったことはなく、一見意味のない機能追加になります...w

ただし...!

このAPIがリリースされる...ということは、外部からNotionを扱いやすくなる...と言うことです...!

そうなることで何が嬉しいのかと言うと、今まではNotionが公式で出したアプリやWebページでしかNotionを自由に扱えなかった(細かい値の編集やページの作成等)のですが、外部サービス等から簡単にNotionのページを作成したり、DBの値を取得したり...といったことができるようになります。

そうなることによって、個人レベルでより自由にNotionを活用した独自のアプリやサービスを作成することができるようになります。

もし、あなたがAPIって何・・・?状態の場合はこの記事を読んでもあまり意味がないかもしれません...

ボタンひとつでできることになんでこんなに手間かけんの...?ってなると思います...w

(APIでできることはnotionのテーブルへpageを追加する...といったnotionの基本機能のため...)

ただ、このAPIが追加されたことによってこれから非純正のNotion便利アプリが増えるんだ...!程度に思ってもらえれば良いかと思います...!(きっと色々な開発者の皆様が作ってくれます...!他力本願...!)

では、開発をやってみよう!

と言う方は簡単にAPIを試していきましょう!

NotionのAPIを試してみよう!

基本的には以下の記事を参考にして試しました。(リンク先は英語になります)

実際に手元で試して、データベースに適当な値を書き込むところを目標としてやっていきます。

Notionのintegrationを追加しよう!

まずはNotionのAPIを使えるようにintegrationの登録を行います。

このintegrationと言うのは、使用するAPIの設定を扱うために使うbot的なものです。

例えばNotion側でリクエストを受ける際、ちゃんと正しいAPIのリクエストになっていることを示すための認証情報が必要になります。

そのため、その際の認証情報などをintegrationで作成していきます。

また、サービスにもよりますが、このIntegrationでAPIがアクセスできる範囲を制御したりする場合もあります(DBの値の上書きや追加はできないが、閲覧はできるAPIにする...等)。

以下の画面へアクセスし、"New Integration"を選択してください。

筆者はすでに一つ作っているのですが、画面左の "New integration"から作成が可能です。

Nameはなんでも大丈夫です。

今回は"TakaponTestAPI"としておきます。

イメージでは、そのAPIが何をするのか、どんなサービスで使われるのか?がわかりやすい形にしておくと良いかと。

LogoはあってもなくてもOKです。

Assosciated workspaceはAPIを使用したいworkspaceを選択します。

一通り選択したらsubmitを。

すると、以下のような画面が出てきます。

SecretsにInternal Integration Tokenと言うものがあります。

この"Show"を選択するとコピーができるので、手元に保管しておいてください。(後で無くしたとしても本画面でいつでも確認できます)

このTokenと言うのが、APIを使うときに一緒に送るべき認証情報になります。

Tokenが一致した場合のみAPIのリクエストが正しいと判断することで悪意のあるユーザーが悪用できないようにしているわけです。

そのため、このTokenは流出させないようにお気をつけください。

Basic informationには先程のName、Logoがあります。

また、続きに...

integration typeと言うものがあります。

基本的にはInternal integrationで大丈夫です。

一通り確認・編集・修正したらSave changesを押しておきましょう。

これで準備OKです!

テスト用のDBを作る

では、先程APIで指定したWorkspaceにて、適当なページ、DBを作りましょう。

筆者はTestingページに以下のようにinlineのテーブルを二つ作りました。

(と言うか、前の解説の残り物です...w)

次に、先程作成したintegrationでこのページにアクセスできるように権限を付与してあげます。

これは、このページのDBに対してAPIを使ってページを追加する際、編集権限がないといけないため、Integrationに対して編集権限を付与してあげる必要があるため行います。

画面右上の"Share"からinviteを選択し...

先程作ったIntegration, "TakaponTestAPI"を選択します。

追加後、上記のように"TakaponTestAPI"が"Can edit"となっていればOKです!

次にAPIで操作する対象となるデータベースIDを取得します。

Notionをアプリで開いている方は、URLをコピーします。

テーブル右上の"・・・"から、"copy link"を選択します。

実際に筆者の場合にコピーされたものは以下です。

https://www.notion.so/21af4f4853784617903f7dff58364db2?v=fb8d7c4863984a479d017e933e074265

このうち、"notion.so/" の後ろから、"?"までの文字列がどのデータベースかを表すIDになります。

それをコピペしましょう。

今回の場合は以下になります。

21af4f4853784617903f7dff58364db2

ここまで準備できたらあとはAPIを叩くだけです...!

NotionのAPIを叩く!

それではAPIを叩いてみます!

APIを叩くためにはcurlコマンドと言うものを用います。

LaunchPadでターミナルを選択し、コマンドを実行できるターミナルを開きましょう。

実際に叩くコマンドは公式からのコピペになります。

curlコマンドはMac OS Xに標準で入っていると思います。

curl -X POST https://api.notion.com/v1/pages \
  -H "Authorization: Bearer {MY_NOTION_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "Notion-Version: 2021-05-13" \
  --data '{
    "parent": { "database_id": "{DATABASE_ID}" },
    "properties": {
      "Name": {
        "title": [
          {
            "text": {
              "content": "Yurts in Big Sur, California"
            }
          }
        ]
      }
    }
  }'

ただし、一部"{MY_NOTION_TOKEN}"と、"{DATABASE_ID}"に関しては、ご自身の情報に置き換える必要があります。

MY_NOTION_TOKENは先程integrationで作った"Internal Integration Token"をコピペします。("{}"も含めて置き換えてください)

そして、DATABASE_IDも先程取得したデータベースIDに置き換えます。("{}"も含めて置き換えてください)

今回の場合は以下のような感じです。

    "parent": { "database_id": "21af4f4853784617903f7dff58364db2" },

※MY_NOTION_TOKENは先程いった通り、秘匿情報です。誰かと共有する際は気をつけてください...!

では、それぞれを入れた形で実行してみましょう...!

(base) taka@Taka Practice % curl -X POST https://api.notion.com/v1/pages \
  -H "Authorization: Bearer {筆者のTOKEN}" \
  -H "Content-Type: application/json" \
  -H "Notion-Version: 2021-05-13" \
  --data '{
    "parent": { "database_id": "21af4f4853784617903f7dff58364db2" },
    "properties": {
      "Name": {
        "title": [
          {
            "text": {
              "content": "Yurts in Big Sur, California"
            }
          }
        ]
      }
    }
  }'
{"object":"page","id":"e24b33a6-1730-4737-993d-54e5019e5564","created_time":"2021-05-16T11:30:34.027Z","last_edited_time":"2021-05-16T11:30:34.027Z","parent":{"type":"database_id","database_id":"21af4f48-5378-4617-903f-7dff58364db2"},"archived":false,"properties":{"Column":{"id":"w`jG","type":"relation","relation":[]},"Name":{"id":"title","type":"title","title":[{"type":"text","text":{"content":"Yurts in Big Sur, California","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"Yurts in Big Sur, California","href":null}]}}}%
(base) taka@Taka Practice %

上記が実行結果になります。

応答内容でぱっと見...

ページが作られてそのidは"e24b33a6-1730-4737-993d-54e5019e5564"で、作成日時が...

といった情報が帰ってきているみたいです。

以下の行ですね。

{"object":"page","id":"e24b33a6-1730-4737-993d-54e5019e5564"........

詳細は割愛します。

では、次にNotionのページもみてみると...?

商品テーブルに一つページが追加されていますね...!

APIを使ってページの追加ができました...!

また、そのタイトルは先程のリクエスト内容の以下"content"で指定された文字列になっていることがわかります。

試しに日本語にして実行してみればちゃんと新しい名前でページが追加されると思うので是非お試しください...!

            "text": {
              "content": "Yurts in Big Sur, California"
            }

以上が基本的なNotionのAPIの使い方です!

うまくいかない場合

あまりうまくいかない場合はないかなと思いますが、念の為...

考えられることとして、Tokenが間違っている場合は以下のようになります。(Tokenを"fakeToken"としてみた)

(base) taka@Taka Practice % curl -X POST https://api.notion.com/v1/pages \
  -H "Authorization: Bearer fakeToken" \
  -H "Content-Type: application/json" \
  -H "Notion-Version: 2021-05-13" \
  --data '{
    "parent": { "database_id": "21af4f4853784617903f7dff58364db2" },
    "properties": {
      "Name": {
        "title": [
          {
            "text": {
              "content": "Yurts in Big Sur, California"
            }
          }
        ]
      }
    }
  }'
{"object":"error","status":401,"code":"unauthorized","message":"API token is invalid."}%

"API token is invalid."からもわかりますが、TOKENが無効だよ〜と怒られています。

また、リクエストの中身(JSON)がおかしい場合は以下のようになります。

(base) taka@Taka Practice % curl -X POST https://api.notion.com/v1/pages \
  -H "Authorization: Bearer {筆者のTOKEN}" \
  -H "Content-Type: application/json" \
  -H "Notion-Version: 2021-05-13" \
  --data '{
    "parent": { "database_id": "21af4f4853784617903f7dff58364db2" },
    "properties": {
      "Name": {
        "title": [
          {
            "text": {
              "content": "Yurts in Big Sur, California"
            }
            リクエストをおかしくしてやるぞ〜!
          }
        ]
      }
    }
  }'
{"object":"error","status":400,"code":"invalid_json","message":"Error parsing JSON body."}%

"Error parsing JSON body."というエラーが帰ってきているのがわかりますね。

内容見直せ...ってことですね。

ぱっと見エラーになるとしたら上記二点あたりが中心になるかと思います。

また、notion側で問題がある場合もあるので、このエラーメッセージを元に判断するといいかと思います。

また、エラーメッセージについては以下で詳述されています。

必要になったらみる...程度でもいいと思いますが、お時間のある方は是非、ご一読ください。

もう一歩深めに触ってみる...!

さて、基本はわかったのですが、もう一歩深堀しておきましょう...!

といっても、簡単にできそうなget系だけ触ってみます。

データベースの情報取得

データベースの取得は以下でできました。

(base) taka@Taka Practice % curl 'https://api.notion.com/v1/databases/21af4f4853784617903f7dff58364db2' \
  -H 'Authorization: Bearer '"{筆者のTOKEN}"'' \
  -H 'Notion-Version: 2021-05-13'

{"object":"database","id":"21af4f48-5378-4617-903f-7dff58364db2","created_time":"2021-05-12T22:02:49.807Z","last_edited_time":"2021-05-16T12:20:00.000Z","title":[{"type":"text","text":{"content":"商品テーブル","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"商品テーブル","href":null}],"properties":{"Price":{"id":"?[he","type":"number","number":{"format":"yen"}},"Column":{"id":"w`jG","type":"relation","relation":{"database_id":"6206756a-4cc8-48fe-9ee6-12a6a3c33c06","synced_property_name":"Related to 商品テーブル (Column)","synced_property_id":"Y[NV"}},"Name":{"id":"title","type":"title","title":{}}}}%

TOKENの設定と、endpointにDATABASE_IDを指定してあげることで取得ができますね。

https://api.notion.com/v1/databases/{DATABASE_ID}

詳細は以下をご確認ください。

ページの取得

ページの情報の取得は以下でできました。

(base) taka@Taka Practice % curl 'https://api.notion.com/v1/pages/6e864ce350a4481586d7ab62b6ecb497' \
  -H 'Notion-Version: 2021-05-13' \
  -H 'Authorization: Bearer '"{筆者のTOKEN}"''
{"object":"page","id":"6e864ce3-50a4-4815-86d7-ab62b6ecb497","created_time":"2021-05-16T12:39:25.306Z","last_edited_time":"2021-05-16T13:01:00.000Z","parent":{"type":"database_id","database_id":"21af4f48-5378-4617-903f-7dff58364db2"},"archived":false,"properties":{"Column":{"id":"w`jG","type":"relation","relation":[]},"Name":{"id":"title","type":"title","title":[{"type":"text","text":{"content":"Yurts in Big Sur, California","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"Yurts in Big Sur, California","href":null}]}}}%

endpointは以下のようになっています。

https://api.notion.com/v1/pages/{取得したいpageID}

値は色々帰ってきていますが、そこの説明は割愛...!

ページIDはDATABASE_IDと同じく、アプリの画面のcopylinkから取得可能です。

(APIでページを新規作成した場合はその帰り値にもありましたね。)

詳細は以下ご参考ください。

ページ一覧取得できるかなぁ...?と試しに以下やってみましたが、流石に無理でした。

https://api.notion.com/v1/pages

流石に件数が多すぎて...って言うことで、そもそもそのAPIを用意していないんだと思います。

そのうちある程度絞ったページ一覧取得とかはでてくるかも?ですね。

ユーザーの取得

次はユーザー周り。

いったん一覧の取得を行なってみます。

(base) taka@Taka Practice % curl 'https://api.notion.com/v1/users' \
  -H 'Authorization: Bearer '"{筆者のTOKEN}"'' \
  -H "Notion-Version 2021-05-13"
{"object":"list","results":[{"object":"user","id":"{筆者のUSERID}","name":"TAKA PON","avatar_url":null,"type":"person","person":{"email":"{筆者のメールアドレス}"}},......

usersのAPIを叩くとUSERIDが取得できました!

(筆者は個人利用なので一部隠しています...自分のアカウントと、先程作ったAPIのintegrationの情報だけですが...)

ぱっと見アプリの方のマイアカウント等からUSERIDを探そうとしたのですが、取得できなさそう...?な雰囲気でした。

そのため、後述するUSERIDを指定しての情報取得はいったん一覧APIで取得する必要がありそうです。

次に、USERIDを指定して特定ユーザーの情報を取得してみます。

先程一覧で取得した"筆者のUSERID"を指定します。

(base) taka@Taka Practice % curl 'https://api.notion.com/v1/users/{筆者のUSERID}' \
  -H 'Authorization: Bearer '"{筆者のTOKEN}"'' \
  -H "Notion-Version 2021-05-13"
{"object":"user","id":"{筆者のUSERID}","name":"TAKA PON","avatar_url":null,"type":"person","person":{"email":"{筆者のメールアドレス}"}}%

一人分だけ取得できました...!

詳細は以下あたりをご参考ください。

子blockの取得

最後に小ブロックの取得です。

block_idが必要になりますね。

block_idってなんだろう?って感じだったんですが、page_idで代替してみると動きました。

おそらく、細かいブロック(calloutや見出し等...)、pageやdatabaseにもidが割り振られていて、それぞれ意味合い的には同じその要素を一意に判別するためのもので、ブロック種別によってNotionの解説ページでの呼び方を変えているだけかなと思います。

なので、block_idとpage_idは意味合い的には一緒で、page_idの方がより狭義なもの...と思っていいのかなと。(たぶん。)

なので、Notionの解説に書かれているblock_idはそのblockのtypeがpageであれば、page_idとして呼ぶこともある...といった具合でしょう。

(base) taka@Taka Practice % curl 'https://api.notion.com/v1/blocks/6e864ce350a4481586d7ab62b6ecb497/children' \
  -H 'Authorization: Bearer '"{筆者のTOKEN}"'' \
  -H "Notion-Version 2021-05-13"
{"object":"list","results":[{"object":"block","id":"9aaeaf54-0d7c-4cde-add4-006c095ecb63","created_time":"2021-05-16T13:47:31.534Z","last_edited_time":"2021-05-16T13:47:00.000Z","has_children":false,"type":"paragraph","paragraph":{"text":[{"type":"text","text":{"content":"hoge","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"hoge","href":null}]}},{"object":"block","id":"8ac531cb-706f-49b0-aca4-3537b97009e6","created_time":"2021-05-16T13:47:00.000Z","last_edited_time":"2021-05-16T13:47:00.000Z","has_children":false,"type":"paragraph","paragraph":{"text":[]}},{"object":"block","id":"773bb58a-4ed6-4d0e-9449-2aa407e6267e","created_time":"2021-05-16T13:47:35.287Z","last_edited_time":"2021-05-16T13:47:00.000Z","has_children":false,"type":"to_do","to_do":{"text":[{"type":"text","text":{"content":"TODO","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"TODO","href":null}],"checked":false}},{"object":"block","id":"7e93e5ea-f821-4d15-af2e-999e14cee0aa","created_time":"2021-05-16T13:47:39.720Z","last_edited_time":"2021-05-16T13:47:00.000Z","has_children":false,"type":"heading_1","heading_1":{"text":[{"type":"text","text":{"content":"HEAD1","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"HEAD1","href":null}]}},{"object":"block","id":"ef2d3a56-d30e-4bd7-a78e-74c16f68b7c8","created_time":"2021-05-16T13:47:45.129Z","last_edited_time":"2021-05-16T13:47:00.000Z","has_children":false,"type":"heading_2","heading_2":{"text":[{"type":"text","text":{"content":"HEAD2","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"HEAD2","href":null}]}},{"object":"block","id":"9d2c7bff-3729-4b84-8a3c-114b8fd45ce7","created_time":"2021-05-16T13:47:00.000Z","last_edited_time":"2021-05-16T13:47:00.000Z","has_children":false,"type":"paragraph","paragraph":{"text":[]}}],"next_cursor":null,"has_more":false}%

ここはNotion特有になりそうなので、レスポンス内容も簡単にみておこうかなと思います。

今回はpage_idを指定しての子blodkの取得を行いました。

対象のページの構成は以下のようにしています。

レスポンスの内容を整形すると...

{
	"object": "list",
	"results": [
		{
			"object": "block",
			"id": "9aaeaf54-0d7c-4cde-add4-006c095ecb63",
			"created_time": "2021-05-16T13:47:31.534Z",
			"last_edited_time": "2021-05-16T13:47:00.000Z",
			"has_children": false,
			"type": "paragraph",
			"paragraph": {
				"text": [
					{
						"type": "text",
						"text": {
							"content": "hoge",
							"link": null
						},
						"annotations": {
							"bold": false,
							"italic": false,
							"strikethrough": false,
							"underline": false,
							"code": false,
							"color": "default"
						},
						"plain_text": "hoge",
						"href": null
					}
				]
			}
		},
		{
			"object": "block",
			"id": "8ac531cb-706f-49b0-aca4-3537b97009e6",
			"created_time": "2021-05-16T13:47:00.000Z",
			"last_edited_time": "2021-05-16T13:47:00.000Z",
			"has_children": false,
			"type": "paragraph",
			"paragraph": {
				"text": []
			}
		},
		{
			"object": "block",
			"id": "773bb58a-4ed6-4d0e-9449-2aa407e6267e",
			"created_time": "2021-05-16T13:47:35.287Z",
			"last_edited_time": "2021-05-16T13:47:00.000Z",
			"has_children": false,
			"type": "to_do",
			"to_do": {
				"text": [
					{
						"type": "text",
						"text": {
							"content": "TODO",
							"link": null
						},
						"annotations": {
							"bold": false,
							"italic": false,
							"strikethrough": false,
							"underline": false,
							"code": false,
							"color": "default"
						},
						"plain_text": "TODO",
						"href": null
					}
				],
				"checked": false
			}
		},
		{
			"object": "block",
			"id": "7e93e5ea-f821-4d15-af2e-999e14cee0aa",
			"created_time": "2021-05-16T13:47:39.720Z",
			"last_edited_time": "2021-05-16T13:47:00.000Z",
			"has_children": false,
			"type": "heading_1",
			"heading_1": {
				"text": [
					{
						"type": "text",
						"text": {
							"content": "HEAD1",
							"link": null
						},
						"annotations": {
							"bold": false,
							"italic": false,
							"strikethrough": false,
							"underline": false,
							"code": false,
							"color": "default"
						},
						"plain_text": "HEAD1",
						"href": null
					}
				]
			}
		},
		{
			"object": "block",
			"id": "ef2d3a56-d30e-4bd7-a78e-74c16f68b7c8",
			"created_time": "2021-05-16T13:47:45.129Z",
			"last_edited_time": "2021-05-16T13:47:00.000Z",
			"has_children": false,
			"type": "heading_2",
			"heading_2": {
				"text": [
					{
						"type": "text",
						"text": {
							"content": "HEAD2",
							"link": null
						},
						"annotations": {
							"bold": false,
							"italic": false,
							"strikethrough": false,
							"underline": false,
							"code": false,
							"color": "default"
						},
						"plain_text": "HEAD2",
						"href": null
					}
				]
			}
		},
		{
			"object": "block",
			"id": "9d2c7bff-3729-4b84-8a3c-114b8fd45ce7",
			"created_time": "2021-05-16T13:47:00.000Z",
			"last_edited_time": "2021-05-16T13:47:00.000Z",
			"has_children": false,
			"type": "paragraph",
			"paragraph": {
				"text": []
			}
		}
	],
	"next_cursor": null,
	"has_more": false
}

めちゃくちゃ長くなってしまいましたが...

"type"をみることで、どういった種類のblockなのかがわかりますね。

例えば...paragraphなら通常の段落(空行のみを含む)、to_doならTODOリスト、heading_1なら大見出し...といった具合です。

さらに、それぞれの"content"では、どういった値(文字)が入っているのかもわかります。

blockも一意なIDが割り振られているっぽいので、今後、指定したblockを別のpageやblockへコピー...といったAPIも追加されそうですね。

また、以下をみることで、そのblock_idを指定してさらに子ブロックを取得する...といったこともできそうですね。

"has_children": false,

詳細については以下をご参考ください。

利用制限

最後にAPIの利用制限についてみておきます。

APIの利用に関してですが、運営側ではレスポンスを返すためのコストがかかっていたり、負荷がかかっていたりします。

大抵のAPIは基本的に無料で使用できますが、一部のみ利用可だったり、使用料をとっていたり、無料だけど回数制限がついていることが一般的で、それはこのNotionAPIも例外ではありません。

以下、Errorsのページの下の方に制限について記載されています。

現状、"3 requests per second"とあるので、秒間3リクエストを越えなければ頻度的には大丈夫そうですね。

回数の上限等は特になさそうです。

ただし...

In the future, Notion plans to adjust rate limits to balance for demand and reliability. Notion may also introduce distinct rate limits for workspaces in different pricing plans.

とあります。

現在のAPI公開内容から、どれくらいの負荷になるのか・・・?等、現在色々と試行錯誤をしているため、これからプランごとに別々の利用制限をかけたりするかも知んないよ〜っということですね。

本記事の内容はあくまで投稿日時時点での内容になるため、詳細は都度、公式ページを確認するようにしてください。

また、リクエストのサイズで制限がかかる場合もあるそうです。

例えば1000文字以上のURLをプロパティとして設定する場合...などはエラーになります。

そのため、そういった制限に引っかからないことを保証してサービス設計の上、APIを利用する必要があるので気をつけてください...!

まとめ

さて、今回はNotionで新しく追加されたAPIについて簡単に触ってみました...!

ほとんどのNotionユーザーには直接的に関係のないことかもしれませんが、このAPI公開によってNotionがさらに便利になっていくのは確実です...!

筆者も時間があれば便利アプリとか作りたいですね...

開発者の皆さんは是非...! 便利アプリ・サービス作ってください...!(丸投げ...w

それでわ!

おすすめの記事