Recall.aiとは、ZoomやGoogle Meet、Microsoft Teamsなどの会議にボットを参加させ、録画や文字起こしをAPIで取得できるサービスです。商談の内容を自動でCRMに残す仕組みの部品としてよく使われます。

会議が終わったら要約をZoho CRMの商談に自動で残す、という構成を組もうとして、Recall.aiの要約を取るはずのAPIが404や400を返す、というご相談がありました。原因は、Recall.aiのAPIがv1.11に変わり、要約(Intelligence)系の機能がAPIから外れたことです。

この記事では、当社が商談記録の自動化を検討する中で調べた内容をもとに、要約が取れない理由と、文字起こし+LLM要約に切り替えてCRMへ記録する方法を解説します。

症状:要約を取るAPIが404・400を返す

v1.10の時代の記事やサンプルコードでは、次のような流れで要約を取得していました。

  1. 会議の後に、ボットの録画に対して分析(analyze)を依頼する
  2. 分析が終わったら、インテリジェンス(要約など)の結果を取得する

当社が2026年4月に調べた範囲では、v1.11のワークスペースでこれらの旧エンドポイントを呼ぶと、次の応答になりました(公式ドキュメントに応答コードの明記はなく、現在の挙動は変わっている可能性があります)。

v1.10の処理 エンドポイント v1.11での応答
録画の分析を依頼 POST /api/v2beta/bot/{id}/analyze 404
要約などの結果を取得 GET /api/v1/bot/{bot_id}/intelligence/ 400(legacy endpoint)
分析ジョブの確認 GET /api/v1/analysis/job/{id}/ 404

v1.10の時代の書き方のままでは、要約を取得できません。

なぜRecall.ai APIで要約が取れないのか:v1.11は要約を返さない

v1.11では、APIのデータの持ち方が「ボット中心」から「録画(Recording)中心」に組み替えられました。録画が基本の単位になり、そこに文字起こし・動画・音声などがぶら下がる形です。

そのうえで、Recall.aiの公式ドキュメント(Async Transcription)には、要約が必要なら文字起こしを外部のLLMに渡して分析する必要があり、Recall.ai自体は文字起こしの要約に対応していないと書かれています(2026年10月7日に確認)。

外部の文字起こしプロバイダー(AssemblyAIなど)を使う場合は、文字起こしの data.provider_data_download_url から、プロバイダーが返した生データを取得できます。プロバイダー側で要約を有効にしていれば、その中に要約が入る可能性はあります。

ただし、Recall.ai自社の文字起こしを使う場合、この生データは提供されません。 Recall.ai自社の文字起こしだけで運用しているなら、Recall.aiから要約を取り出す手段はない、ということです。

なお、v1.11のリリースノートには「APIバージョン間で機能の差はない」とあります。これは「要約の機能を残した」という意味ではなく、データへのたどり着き方が変わったと読むのが妥当です。また、v1.10を使えるのは以前から使っていた一部のレガシーワークスペースだけとされており、新しいワークスペースを旧版に戻すことはできません。

会議の要約はどう取ればよいか:文字起こしを取得し、LLMで要約する

会議の録画から文字起こしを取得し、それをLLMで要約して、Zoho CRMの記録にメモとして残すまでの4段の流れを示した図。

切り替え後の流れは次のとおりです。

  1. ボットを作成する(会議URLと、CRMのどのレコードの会議かを metadata に入れる)
  2. 会議が終わると recording.done のWebhookが届く
  3. 文字起こしが完了すると transcript.done のWebhookが届く
  4. GET /api/v1/transcript/{transcript_id}/ で文字起こしの情報を取り、data.download_url から本文(JSON)をダウンロードする
  5. 本文をLLMに渡して要約を作る
  6. 要約をZoho CRMの商談などにメモとして書き込む

会議の後で文字起こしを作る場合は、recording.done を受けたあとに POST /api/v1/recording/{recording_id}/create_transcript/ で文字起こしを依頼します。公式ドキュメントでは、1つの録画につき成功した文字起こしは同時に10件まで(試行は100件まで)とされています。同じ録画に何度も依頼しないよう、処理済みかどうかを記録しておきます。

Webhookは、Recall.aiの管理画面(Webhooksダッシュボード)で受け取り先のURLと、受け取るイベントを1つずつ登録します。

実装パターンは3つ

パターン 仕組み 向いているケース
1. サーバーで処理 transcript.done を自前のサーバーやクラウド関数で受け、要約・CRM書き込みまで行う 最も制御しやすい。エラー時の再実行や記録の形を細かく決めたい
2. iPaaSで処理 Zoho Flow・Make・ZapierなどでWebhookを受け、LLM呼び出しとCRM書き込みをフローで組む コードを書かずに始めたい。会議数が多くない
3. リアルタイム要約 ボット作成時に recording_config.realtime_endpoints を設定し、transcript.data を会議中に受け取りながら要約する 会議の直後に要約を見せたい。実装は最も複雑

まずはパターン1か2で始めるのがおすすめです。パターン3は、会議中に発言を受け取り続ける仕組みが必要になるため、要件がはっきりしてから検討します。

パターン2を使う場合、長い会議では文字起こしの文字数が大きくなります。LLMに渡せる量や料金、iPaaS側の1回あたりのデータ量の上限を、事前に確かめてください。

パターン1のコード例(Node.js)

ここでは、transcript.done を受けて要約を作り、Zoho CRMの商談にメモを追加する最小構成を示します。Node.js 18以降(fetch が標準で使える版)を想定しています。

1. ボット作成時に商談IDを metadata に入れる

Recall.aiのボット作成APIは、文字列の組み合わせを metadata として受け取れます。ここにCRMの商談IDを入れておくと、Webhookの data.bot.metadata で受け取れるため、どの商談の会議かを後から判定できます。

curl -X POST "https://ap-northeast-1.recall.ai/api/v1/bot/" \
  -H "Authorization: Token $RECALL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "meeting_url": "(会議のURL)",
    "bot_name": "議事録ボット",
    "metadata": { "crm_module": "Deals", "crm_record_id": "(商談のレコードID)" }
  }'

Recall.aiは地域ごとに別の環境になっていて、日本の環境のURLは https://ap-northeast-1.recall.ai です。APIキーやデータは地域ごとに別なので、契約した地域のURLを使います。

ボットの作成時に、文字起こしの設定(recording_config.transcript)も指定しておきます。指定方法は使うプロバイダーで変わるため、公式ドキュメントの最新の例に合わせてください。

2. Webhookを受けて、要約してCRMに書き込む

コード中のLLMのモデルIDとパラメータは、執筆時点(2026年10月)のものです。モデルは入れ替わるため、環境変数 LLM_MODEL で指定できるようにしています。使う前に、各LLMの公式ドキュメントで最新のモデルIDと書き方を確認してください。

// server.mjs  (npm install @anthropic-ai/sdk)
import http from "node:http";
import Anthropic from "@anthropic-ai/sdk";

const RECALL_BASE = process.env.RECALL_BASE_URL;      // 例: https://ap-northeast-1.recall.ai
const RECALL_KEY = process.env.RECALL_API_KEY;
const ZOHO_API = process.env.ZOHO_API_DOMAIN;          // 例: https://www.zohoapis.jp
const ZOHO_ACCOUNTS = process.env.ZOHO_ACCOUNTS_URL;   // 例: https://accounts.zoho.jp

const anthropic = new Anthropic(); // ANTHROPIC_API_KEY を環境変数から読む

// --- Recall.ai:文字起こしを取得して、話者つきの文章にする ---
async function fetchTranscriptText(transcriptId) {
  const res = await fetch(`${RECALL_BASE}/api/v1/transcript/${transcriptId}/`, {
    headers: { Authorization: `Token ${RECALL_KEY}` },
  });
  if (!res.ok) throw new Error(`transcript ${res.status}`);
  const transcript = await res.json();

  // download_url は署名付きURLを想定し、認証ヘッダを付けていない(当社の想定で、実機では未確認)。
  // 403 が返る環境では、Authorization ヘッダを付けて取得し直す
  const dl = await fetch(transcript.data.download_url);
  if (!dl.ok) throw new Error(`download ${dl.status}`);
  const segments = await dl.json(); // [{ participant: { name }, words: [{ text }] }, ...]

  return segments
    .map((s) => `${s.participant?.name ?? "不明"}: ${s.words.map((w) => w.text).join(" ")}`)
    .join("\n");
}

// --- LLM:要約を作る(別のLLMに替えるときはこの関数だけ差し替える)---
async function summarize(text) {
  const response = await anthropic.messages.create({
    model: process.env.LLM_MODEL ?? "claude-opus-5-5", // 執筆時点のモデルID
    max_tokens: 16000,
    output_config: { effort: "low" },
    system:
      "あなたは商談の議事録係です。会議の文字起こしから、日本語で次の4項目を箇条書きでまとめてください。" +
      "1. 議題と結論 2. 決まったこと 3. 次のアクション(担当・期限が分かれば併記) 4. 先方の懸念点。" +
      "文字起こしに書かれていないことは推測で補わないでください。",
    messages: [{ role: "user", content: text }],
  });
  if (response.stop_reason === "refusal") throw new Error("summary refused");
  return response.content
    .filter((b) => b.type === "text")
    .map((b) => b.text)
    .join("\n");
}

// --- Zoho CRM:リフレッシュトークンからアクセストークンを取る ---
async function getZohoToken() {
  const params = new URLSearchParams({
    refresh_token: process.env.ZOHO_REFRESH_TOKEN,
    client_id: process.env.ZOHO_CLIENT_ID,
    client_secret: process.env.ZOHO_CLIENT_SECRET,
    grant_type: "refresh_token",
  });
  const res = await fetch(`${ZOHO_ACCOUNTS}/oauth/v2/token?${params}`, { method: "POST" });
  const json = await res.json();
  if (!json.access_token) throw new Error("zoho token error");
  return json.access_token;
}

// --- Zoho CRM:レコードにメモを追加する ---
async function addNote(module, recordId, title, content) {
  const token = await getZohoToken();
  const res = await fetch(`${ZOHO_API}/crm/v8/${module}/${recordId}/Notes`, {
    method: "POST",
    headers: { Authorization: `Zoho-oauthtoken ${token}`, "Content-Type": "application/json" },
    body: JSON.stringify({ data: [{ Note_Title: title, Note_Content: content }] }),
  });
  if (!res.ok) throw new Error(`zoho notes ${res.status}: ${await res.text()}`);
}

// --- Webhookの受け口 ---
http
  .createServer((req, res) => {
    let body = "";
    req.on("data", (c) => (body += c));
    req.on("end", async () => {
      // 先に200を返し、重い処理はその後で行う(Webhookの再送を防ぐ)
      res.writeHead(200).end();
      try {
        // 本番では、ここで署名を検証し、合わなければ処理しない(下の説明を参照)
        const event = JSON.parse(body);
        if (event.event !== "transcript.done") return;

        const { transcript, bot } = event.data;
        const meta = bot?.metadata ?? {};
        if (!meta.crm_module || !meta.crm_record_id) return; // CRMと紐付かない会議は対象外

        const text = await fetchTranscriptText(transcript.id);
        const summary = await summarize(text);
        await addNote(meta.crm_module, meta.crm_record_id, "会議の要約(自動作成)", summary);
      } catch (e) {
        console.error(e); // 実運用では失敗した transcript.id を記録し、再実行できるようにする
      }
    });
  })
  .listen(process.env.PORT ?? 8080);

このコードは、届いたWebhookをそのまま信じて処理しています。本番では、Recall.aiから届いたものかを必ず確かめてください。Recall.aiの公式ドキュメントでは、ダッシュボードで検証用のシークレットを作ると、Webhookに識別子・時刻・署名のヘッダが付くようになると説明されています。受け手は「識別子.時刻.受け取った本文(加工前の文字列)」をシークレットでHMAC-SHA256にかけ、ヘッダの署名と一致するかを比べます。一致しないものや、時刻が大きくずれているものは捨てます。ヘッダ名はアカウントを作った時期で異なるため、公式ドキュメントで確認してください。

コードの中で、Zoho CRMの書き込み先は商談(Deals)を想定しています。取引先や見込み客に残したい場合は、ボット作成時の crm_module を変えるだけで同じ処理が使えます。Zoho CRMのAPIを使うには、メモの作成と対象モジュールの作成権限を含むスコープで認証しておく必要があります。

APIキー・クライアントシークレット・リフレッシュトークンは、必ず環境変数やシークレット管理の仕組みに置き、コードに直接書かないでください。Zoho CRMのAPIを使うための認証の準備は「Zoho APIをスクリプトから叩く最短ルート|Self Client / Client Credentials設定」で解説しています。

運用でつまずきやすい点

会議とCRMのレコードをどう結び付けるか

いちばん悩むのはここです。今回の例のように、ボットを作る時点で商談IDを metadata に入れられるなら簡単です。CRMの商談画面からボットを呼ぶボタンを用意する、といった作り方が考えられます。

カレンダー連携で自動参加させる場合は、参加者のメールアドレスとCRMの連絡先を突き合わせる方法もあります。ただし、相手が個人のアドレスで参加した場合や、CRMに登録していないアドレスの場合は紐付かないため、紐付かなかった会議をどう扱うかのルールを先に決めておきます。

要約の形を決めておく

LLMへの指示(プロンプト)で、要約の項目を固定しておくと、CRM上で読みやすくなります。上のコードでは「議題と結論」「決まったこと」「次のアクション」「先方の懸念点」の4項目にしています。

さらに、決まった形式で書かせれば、要約から金額や次回アクションを項目に反映することもできます。その方法はZoho CRMで議事録メモから商談情報を自動更新する|Delugeカスタム関数の実装例で解説しています。

失敗したときに再実行できるようにする

文字起こしの取得、LLMの呼び出し、CRMへの書き込みのどこかで失敗すると、その会議の要約は残りません。失敗した transcript.id を記録しておき、あとから同じ処理をやり直せるようにしておくと安心です。文字起こしそのものが失敗した場合は transcript.failed のWebhookが届くので、これも受け取って通知するようにします。

文字起こしの日本語の精度

要約の質は、文字起こしの質に左右されます。固有名詞や専門用語が多い会議では、プロバイダーごとに精度が変わることがあります。本格運用の前に、実際の会議数件で文字起こしと要約を確かめ、必要ならプロバイダーやプロンプトを調整してください。会議ではなく電話の文字起こしをCRMに残す場合の比較は「Dialpadの文字起こしが月額3,000円に。Zoho CRM連携なら「Zoom Phone最安プラン」で代替できるか検証してみた」で紹介しています。

外部プロバイダーの要約を使うという選択肢

AssemblyAIなど要約機能を持つ文字起こしプロバイダーを使い、provider_data_download_url から要約を取り出す方法もあります。LLMを別に呼ばなくて済むのが利点です。

一方で、データの形はプロバイダーごとに異なり、Recall.aiの共通形式では提供されません。プロバイダーを変えると取り出し方も変わります。要約の項目を自社の運用に合わせて細かく決めたい場合は、本文で紹介したLLM要約のほうが扱いやすいと当社は考えています。

まとめ

  • Recall.aiの現行API(v1.11)は、会議の要約を返さない
  • 旧版の analyze/intelligence 系エンドポイントは、v1.11では使えない
  • Recall.ai自社の文字起こしでは、プロバイダーの生データもない
  • 対処は「transcript.done →文字起こし取得→LLM要約→CRMへ書き込み」
  • 会議とCRMの紐付けは、ボット作成時の metadata に商談IDを入れておくのが簡単

外部サービスのAPIは、版が変わると古い記事の手順が通らなくなります。実装の前に、使う版の公式ドキュメントで手順を確かめてください。AIでCRMへの記録を自動化する全体の流れは「Zoho CRM × AIエージェント:2026年の自動化トレンド」でも整理しています。Recall.aiとZoho CRMの連携や、商談記録の自動化は、Zoho CRM×AI連携カスタマイズサービスで承っています。

よくある質問

Recall.aiのv1.10に戻せば、要約APIを使えますか?

公式のリリースノートでは、v1.10 APIを使えるのは以前から利用していた一部のレガシーワークスペースだけとされています。新しく作ったワークスペースをv1.10に戻す方法は案内されていません。

AssemblyAIなど外部の文字起こしを使えば、要約も取れますか?

外部の文字起こしプロバイダーを使った場合、文字起こしの data.provider_data_download_url からプロバイダーの生データを取得できます。プロバイダー側で要約を有効にしていれば含まれる可能性がありますが、形式はプロバイダーごとに異なります。Recall.ai自社の文字起こしでは、この生データは提供されません。

要約にはどのLLMを使えばよいですか?

特定のLLMである必要はありません。文字起こしの文章を渡して要約を返せるものであれば使えます。この記事のコード例ではClaudeのAPIを使っていますが、要約を作る関数だけを差し替えれば他のLLMにも置き換えられます。

コードを書かずに実現できますか?

Zoho FlowやMake、ZapierなどのiPaaS(サービス同士をつなぐツール)で、WebhookをきっかけにLLMを呼び、CRMに書き込むフローを組めます。長い会議では、LLMに渡す文字数と料金に注意してください。