文字起こしとNotion連携、APIレシピ(2026年版)
apinotionintegrations

文字起こしとNotion連携、APIレシピ(2026年版)

BMMamane B. MoussaMay 26, 2026Updated July 2, 20264 min read

Summarize this article with:

TL;DR

ConvertAudioToTextとNotionのネイティブ連携はまだありません。動く方法はwebhook APIを使うことです。webhookを登録して、job.completedイベントを受け取り、返ってきたURLから文字起こしを取得し、Notionの統合トークンでデータベースに書き込みます。Node.jsなら大体120行、Zapierやn8nならコードなしで済みます。Notion APIの制限を考慮して、長い文字起こしは複数のブロックに分け、リクエストはレート制限をかける必要があります。

作業パス

現在、NotionマーケットプレイスにはConvertAudioToTextのネイティブ統合は存在しません。 存在するのはクリーンなAPIパスです。CATT APIにWebhookエンドポイントを登録し、文字起こしが完了したときにjob.completedイベントを受け取り、返されたURLから文字起こしを取得して、Notionデータベースに書き込みます。約120行のNode.jsと半日のセットアップで、チームが録音したすべてのものが検索可能なNotionコンテンツになります。

ほとんどのチーム構成は3つのパターンでカバーできます。このガイドではデータベース行パターンを完全に解説し、他のパターンについても補足します。

文字起こしごとのデータベース行。 「Transcripts」データベースに、日付、話者、プロジェクト、要約、結果へのリンクの列を用意します。多数の録音をフィルタリングして並べ替えるチームに最適です。

既存のプロジェクトページへの追記。 各文字起こしは既存のプロジェクトページやミーティングページにブロックとして追加されます。文字起こしがより広いプロジェクトノートに属するチームに最適です。

話者またはインタビュー対象者ごとの1ページ。 話者からページIDへのマッピングを維持し、それに応じて文字起こしをルーティングします。定期的に情報源を持つジャーナリストや、主要アカウントを追跡する営業チームに役立ちます。

ステップ1: Notionデータベースを作成する

Notionで、以下のプロパティを持つ新しいフルページデータベースを作成します。

プロパティタイプ用途
タイトルタイトルファイル名またはミーティングのトピック
日付日付録音日
話者マルチセレクト録音に含まれる人物
プロジェクトセレクト所属するプロジェクト
要約テキスト生成された場合の要約
長さ数値音声の長さ(秒)
言語セレクト検出または指定された言語
結果URLURLCATTの結果ページへのリンク

URLからデータベースIDをコピーします。これは最後のスラッシュに続き、?v=クエリパラメータの前に来る32文字の16進文字列です。

ステップ2: Notionインテグレーションを作成する

ステップ3: CATTでWebhookを登録する

ConvertAudioToTextのWebhookを利用するにはBusinessプランが必要です。エンドポイントを登録しましょう:

curl -X POST https://api.convertaudiototext.com/api/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/catt",
    "events": ["job.completed", "job.failed"]
  }'

レスポンスにはsecretフィールドが含まれます。これを環境変数CATT_WEBHOOK_SECRETとして保存してください。この値は作成後に再取得することはできません。

Webhookを使わないポーリング方式の代替案については、トランスクリプトにおけるWebhookとポーリングの比較を参照してください。

ステップ4: Webhookハンドラー

job.completedのペイロードには、完全なトランスクリプトを指すresult_urlフィールドが含まれます。ハンドラーは署名を検証し、イベントタイプを確認し、トランスクリプトを取得して、Notionに書き込みます。

import express from 'express';
import { Client as NotionClient } from '@notionhq/client';
import crypto from 'crypto';

const app = express();
const notion = new NotionClient({ auth: process.env.NOTION_TOKEN });
const NOTION_DB_ID = process.env.NOTION_DATABASE_ID;

app.post(
  '/webhooks/catt',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const sigHeader = req.header('X-Webhook-Signature'); // "sha256=<hex>"
    const [, sig] = (sigHeader || '').split('=');
    const expected = crypto
      .createHmac('sha256', process.env.CATT_WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');

    if (!sig || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
      return res.status(401).send('Bad signature');
    }

    const event = JSON.parse(req.body.toString());
    if (event.event !== 'job.completed') return res.sendStatus(200);

    // 結果URLから完全な文字起こしを取得
    const result = await fetch(event.data.result_url, {
      headers: { Authorization: `Bearer ${process.env.CATT_API_KEY}` }
    }).then(r => r.json());

    await createNotionPage({
      jobId: event.data.job_id,
      language: event.data.language,
      durationSeconds: event.data.duration,
      completedAt: event.data.completed_at,
      resultUrl: event.data.result_url,
      transcript: result.transcript,
      summary: result.summary,
      utterances: result.utterances,
      filename: result.filename,
    });

    res.sendStatus(200);
  }
);

job.completed イベントで常に含まれるフィールドは、event、timestamp、data.job_id、data.status、data.created_at、data.language、data.duration、data.completed_at、data.result_url です。完全な文字起こしテキスト、要約、発話はイベント自体ではなく、結果の取得から得られます。

要約と文字起こし: Notionデータベースが求める2つのブロック要約と文字起こし: Notionデータベースが求める2つのブロック

ステップ5: Notionページを書く

async function createNotionPage(data) {
  const transcriptBlocks = buildTranscriptBlocks(data.utterances || []);


const page = await notion.pages.create({
    parent: { database_id: NOTION_DB_ID },
    properties: {
      Title: {
        title: [{ text: { content: data.filename || `Transcript ${data.completedAt?.slice(0, 10)}` } }]
      },
      Date: { date: { start: data.completedAt?.slice(0, 10) || new Date().toISOString().slice(0, 10) } },
      Language: { select: { name: data.language || 'auto' } },
      Duration: { number: data.durationSeconds },
      'Result URL': { url: data.resultUrl },
      Summary: {
        rich_text: [{ text: { content: (data.summary || '').slice(0, 2000) } }]
      }
    }
  });

  // トランスクリプトのブロックを100件ずつ追加(Notion APIの制限)
  if (transcriptBlocks.length > 0) {
    await appendBlocksInBatches(page.id, transcriptBlocks);
  }
}

function buildTranscriptBlocks(utterances) {
  const blocks = [];

  blocks.push({
    object: 'block',
    type: 'heading_2',
    heading_2: { rich_text: [{ text: { content: 'Transcript' } }] }
  });

  for (const u of utterances) {
    // Notionのrich_text.contentはブロックあたり2,000文字が上限
    const label = u.speaker ? `[${u.speaker}] ` : '';
    const text = (label + (u.text || '')).slice(0, 2000);
    blocks.push({
      object: 'block',
      type: 'paragraph',
      paragraph: { rich_text: [{ text: { content: text } }] }
    });
  }

  return blocks;
}

async function appendBlocksInBatches(pageId, blocks) {
  for (let i = 0; i < blocks.length; i += 100) {
    const batch = blocks.slice(i, i + 100);
    await notion.blocks.children.append({ block_id: pageId, children: batch });
    if (i + 100 < blocks.length) {
      await new Promise(r => setTimeout(r, 400)); // 平均3リクエスト/秒以内に収める
    }
  }
}

1リクエストあたり100ブロックという上限は、Notion APIの厳格な制約です。400msの待機時間を入れることで、平均3リクエスト/秒の制限内に収まります。非常に長い録音(100発話以上)の場合、書き込みに数秒かかりますが、非同期の後処理ステップとしては許容範囲です。

バリエーション: 既存のプロジェクトページに追記する

トランスクリプトを独立したデータベースではなくプロジェクトページ内に収めたい場合は、既存のページIDに対してblocks.children.appendを使います。

async function appendToProjectPage(projectPageId, data) {
  const divider = { object: 'block', type: 'divider', divider: {} };
  const heading = {
    object: 'block',
    type: 'heading_3',
    heading_3: {
      rich_text: [{ text: { content: `Meeting transcript ${data.completedAt?.slice(0, 10)}` } }]
    }
  };
  const transcriptBlocks = buildTranscriptBlocks(data.utterances || []);
  await appendBlocksInBatches(projectPageId, [divider, heading, ...transcriptBlocks]);
}

ノーコードでの実現方法

サーバーを書かずにこれを実現したい場合、今すぐ使える選択肢が2つあります。

Zapier。 フローはこうです: Webhookトリガー(CATTのjob.completedイベントをキャッチ) → Codeステップで結果URLを取得 → Notionで「データベースアイテムを作成」アクション。複数ステップのZapは有料プランが必要です。Zapierはカスタムコードよりセットアップが速いですが、Notionへの書き込みが静かに失敗したときのデバッグが難しいです。

n8n。 公開されているn8nワークフローテンプレートがあり、Whisperの文字起こしをNotionページに連鎖させられます。セルフホスト版のn8nは無料で実行回数無制限、録音データをベンダー所有のクラウドに預けずに済みます。Make.comにも同じように動くNotionモジュールがあります。ステップバイステップのノーコード手順はZapierで文字起こしを自動化するを参照してください。

Fireflies.ai にはネイティブのNotion連携があり(Zapier不要)、録音された各通話の後に会議サマリー、アクションアイテム、トランスクリプトリンクをNotionデータベースへプッシュします。主にファイルアップロードではなくミーティングボットが必要な場合は、これが最適な選択です。

Notion APIバージョンに関する注意(2026)

Notion APIバージョン2026-03-11では、既存の統合をアップグレードする際に知っておくべき破壊的変更が3つ導入されました。

  • append-block-childrenのafterパラメータはpositionに置き換わりました。
  • archivedフィールドはin_trashに置き換わりました。
  • transcriptionブロックタイプはmeeting_notesに名称変更されました。

今日新しいコードを書くなら、@notionhq/clientをv5.12.0以降でインストールし、クライアント構築時にnotionVersion: "2026-03-11"を設定すれば最新の動作を利用できます。従来のトークン形式(secret_プレフィックス)も引き続き動作します。

本番運用のパターン

ジャーナリズムチーム。 録音したインタビューはすべて検索可能なNotionデータベースに保存されます。トピック、ソース、日付でプロパティをフィルタリングすれば、記者は数秒で特定のソースからの全引用を取得できます。インタビュー全体のワークフローについては、インタビュー録音の文字起こし方法をご覧ください。

カスタマーサクセスチーム。 顧客との通話はすべて顧客ごとのNotionページに保存されます。四半期ビジネスレビューの準備が、何時間もの聞き直しから15分の読み物に短縮されます。

ポッドキャスト制作。 各エピソードには、文字起こし、要約、編集用の結果URLを含むNotionページが作成されます。プロデューサーは生の音声ファイルを共有せずにソーシャルチームへ引き継げます。一般的なツール間の精度比較はポッドキャストに最適な文字起こしをご覧ください。

ウェブフックサーバーなしでクリーンな文字起こしだけが必要なら、ConvertAudioToTextがアップロードを処理し、話者ラベル付きの出力を生成します。コピーペーストやエクスポートで、そのまま任意のNotionページに貼り付けられます。

生の文字起こしの上に議事録レイヤーを構築する場合は、音声から議事録を作成で、Notionデータベースのプロパティにきれいにマッピングできる構造化出力パターンを解説しています。

FAQ

ConvertAudioToTextにはNotionのネイティブ統合機能がありますか?

まだです。Notionインテグレーションマーケットプレイスにはワンクリック接続はありません。実用的な方法としては、job.completedイベントを受け取るWebhookエンドポイント(Businessプラン)を登録し、result_url経由で文字起こしを取得して、Notion APIでNotionに書き込むという流れになります。

Notion APIの制限はどのように考慮すべきですか?

Notion APIは、インテグレーションごとに1秒あたり平均3リクエストまで許可されています。各append-block-children呼び出しは最大100ブロックまで受け付けます。ブロックあたりのリッチテキストは2,000文字が上限です。長い文字起こしの場合は、ブロックを100件ずつに分けて、呼び出し間に短い遅延を挟みながらバッチ処理してください。

有料のNotionプランはAPI利用に必要ですか?

いいえ。Notion APIは無料プランを含むすべてのNotionプランで無料です。有料プランが必要になるのは、ゲストとの共有など、特定の制限を超えるNotion機能を使う場合だけです。APIのレート制限は全プランで同じです。

ノーコードで文字起こしをNotionに送る方法は?

良い選択肢が2つあります。Zapier(多段階のZapには有料プランが必要)は、Webhookを受け取ってNotionページを作成するのをコードなしで実現します。n8n CloudにはWhisperからNotionへのテンプレートがあり、セルフホスト版は無料で使えます。どちらもシンプルな文字起こしからページへのフローに適していますが、話者ラベル付きブロックやバッチ処理、長いファイルにはカスタムコードの方が適しています。

出典

Try transcription free

Convert any audio or video to clean, unwatermarked text — speaker labels, timestamps, and AI summaries included. First 30 minutes free, no account.

Related Articles