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の時代の記事やサンプルコードでは、次のような流れで要約を取得していました。
- 会議の後に、ボットの録画に対して分析(analyze)を依頼する
- 分析が終わったら、インテリジェンス(要約など)の結果を取得する
当社が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で要約する

切り替え後の流れは次のとおりです。
- ボットを作成する(会議URLと、CRMのどのレコードの会議かを
metadataに入れる) - 会議が終わると
recording.doneのWebhookが届く - 文字起こしが完了すると
transcript.doneのWebhookが届く GET /api/v1/transcript/{transcript_id}/で文字起こしの情報を取り、data.download_urlから本文(JSON)をダウンロードする- 本文をLLMに渡して要約を作る
- 要約を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に渡す文字数と料金に注意してください。




