conversations について
Zavuの会話REST APIで受信トレイや関連ツールを構築するためのスキルです。チャネル横断のスレッド、検索、メッセージ取得、返信、Webhook更新を説明します。
主な機能
- 01チャネル横断の会話スレッド
- 02スレッド一覧と相手の検索
- 03メッセージ取得と既読状態管理
- 04スレッドの送信元を使った返信
- 05Webhookによる更新
活用シーン
- 01共有受信トレイのスレッド一覧作成
- 02連絡先の会話履歴表示
- 03スレッドの送信元から返信
- 04Webhookで受信トレイを更新
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ zavudev/ zavu-skills --skill conversationsインストール後、Claude Code で/conversationsを入力して読み込まれたか確認します。
詳細
会話
使用するタイミング
共有受信トレイの UI、ヘルプデスクとの同期、「この顧客の履歴を見せて」というツール、返信前にスレッドを取得する必要があるエージェントなど、受信トレイを読み取るものを構築するときに、このスキルを使用してください。
GET /v1/messages は、紐付け先となるスレッドのないフラットなログを返します。会話がスレッドです。
会話とは
1 人の連絡先との 1 つのスレッドで、すべてのチャネルにまたがります。WhatsApp でメッセージを送った後、メールでも連絡してきた連絡先は、同じ会話に留まり、channels に利用されたチャネルが一覧表示されます。
| フィールド | 意味 |
|---|---|
id | スレッド ID。メッセージ、conversation.new、message.inbound の conversationId と同じ値です。 |
contactIdentifier | スレッドの識別に使われるキーです。電話番号とは限りません。詳細は以下を参照してください。 |
contactId | 解決済みの場合の連絡先です。グループスレッドでは存在しません。 |
channels | このスレッドで利用されたすべてのチャネルです。 |
lastMessage | 非正規化されたプレビュー(id、text、channel、direction、at)です。スレッド一覧の取得時に追加のフェッチが不要になります。 |
senderId | スレッドを最後に処理した送信者です。返信時に Zavu-Sender として渡し返してください。 |
unreadCount | まだ既読としてマークされていない受信メッセージ数です。 |
whatsapp | 連絡先が WhatsApp ユーザー名を設定している場合の bsuid と username です。 |
contactIdentifier は電話番号ではありません
スレッドの識別に使われる ID が格納されます。E.164 電話番号、WhatsApp BSUID(US.13491208655302741918)、または数値のチャット ID(Telegram/Instagram/Messenger)のいずれかです。電話番号として解析すると、SMS 以外のすべてのチャネルで問題が起きます。表示方法は channels に基づいて判断してください。
エンドポイント
これらはまだ生成済み SDK に含まれていません。REST を使用してください。
| メソッド | パス | 目的 |
|---|---|---|
GET | /v1/conversations | スレッドを、最近アクティブになった順に一覧表示します。 |
GET | /v1/conversations/{conversationId} | 1 つのスレッドを取得します。 |
GET | /v1/conversations/{conversationId}/messages | スレッド内のメッセージを新しい順に取得します。 |
POST | /v1/conversations/{conversationId}/read | unreadCount をゼロにリセットします。 |
スレッドの一覧表示
curl "https://api.zavu.dev/v1/conversations?limit=25" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"
フィルター:senderId は 1 つの番号に絞り込み、channel はそのチャネルを利用したスレッドだけを残します。
curl "https://api.zavu.dev/v1/conversations?senderId=sender_12345&channel=whatsapp" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"
TypeScript:
const res = await fetch("https://api.zavu.dev/v1/conversations?limit=25", {
headers: { Authorization: `Bearer ${process.env.ZAVUDEV_API_KEY}` },
});
const { items, nextCursor } = await res.json();
スレッドの検索
search は、相手が誰かを基準にスレッドを検索します。電話番号、メールアドレス、WhatsApp ユーザー名、BSUID が対象です。電話番号の形式は問いません。+1 (555) 123-4567 と 15551234567 は、どちらも同じスレッドに一致します。
curl "https://api.zavu.dev/v1/conversations?search=%2B56912345678" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"
# by email, by its local part, or by group name
curl "https://api.zavu.dev/v1/conversations?search=maria" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"
単語全体で照合し、最後の語句は前方一致で検索します。mar は [email protected] に、+1555 は +15551234567 に一致します。番号の途中や末尾の一部(4567)では一致しません。番号全体、またはその先頭部分で検索してください。
利用する前に、次の 3 点を把握しておいてください。
- メッセージ本文は検索しません。
searchが照合するのはスレッドの識別情報であり、スレッド内での発言内容ではありません。 - 結果は新しさではなく関連性の順に並びます。
searchを指定している間は、通常の「最近アクティブになった順」の並び順は適用されません。 - 空の
searchは何も返しません。 すべてのスレッドを一覧表示するには、このパラメーターを省略してください。
search は senderId や channel と組み合わせることができ、ほかの一覧と同様に cursor でページネーションできます。
const params = new URLSearchParams({ search: "+56912345678", channel: "whatsapp" });
const res = await fetch(`https://api.zavu.dev/v1/conversations?${params}`, {
headers: { Authorization: `Bearer ${process.env.ZAVUDEV_API_KEY}` },
});
const { items } = await res.json();
ページネーション
nextCursor は不透明な値です。そのまま cursor として渡し返してください。手作業で生成しないでください。最後のページでは nextCursor は null です。
async function allConversations() {
const out = [];
let cursor: string | undefined;
do {
const url = new URL("https://api.zavu.dev/v1/conversations");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.ZAVUDEV_API_KEY}` },
});
const page = await res.json();
out.push(...page.items);
cursor = page.nextCursor ?? undefined;
} while (cursor);
return out;
}
channel フィルターは各ページの取得後に適用されるため、フィルター後のページは limit より短くなる場合があり、空になることもあります。その場合でも nextCursor は設定されたままです。nextCursor が null になるまでページネーションを続けてください。ページの件数が少なくても終了しないでください。
2 つの方向を見分ける
すべてのメッセージには direction(inbound または outbound)が含まれます。これを使用してください。status では方向を見分けられません。受信メッセージも delivered として保存されるためです。to と contactIdentifier を比較して判定する方法は、1 対 1 のスレッドでは機能しますが、from がスレッドのキーではなく参加者を示すグループでは正しく動作しません。
for (const m of items) {
const mine = m.direction === "outbound";
console.log(`${mine ? "→" : "←"} ${m.text}`);
}
スレッドを読む
curl "https://api.zavu.dev/v1/conversations/$CONV_ID/messages?limit=50" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"
スレッド内のすべてのチャネルを対象に、新しい順で、GET /v1/messages と同じ Message オブジェクトを返します。
既読にする
curl -X POST "https://api.zavu.dev/v1/conversations/$CONV_ID/read" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"
あなたの受信トレイのカウンターをクリアします。連絡先に既読通知は送信されません。WhatsApp の既読通知と入力中インジケーターを表示するには、代わりに受信メッセージに対して POST /v1/messages/{messageId}/typing を使用してください。
スレッドで返信する
POST /v1/messages で送信します。スレッドの識別子を指定し、senderId を Zavu-Sender として渡すことで、連絡先がすでに知っている番号から返信できます。
const conv = await getConversation(conversationId);
await zavu.messages.send({
to: conv.contactIdentifier,
channel: conv.lastMessage.channel,
text: "Thanks — shipping today.",
"Zavu-Sender": conv.senderId,
});
送信元のオーバーライドは、2つ目の引数ではなく、送信パラメーターオブジェクト内のヘッダーパラメーターです。
返信時には、引き続き次の2つの制約があります。
- WhatsApp の24時間ウィンドウ。 期限を過ぎると、自由形式のテキストは
whatsapp_window_closedで拒否されます。代わりに承認済みのテンプレートを送信してください。whatsapp-templatesスキルを参照してください。 - チャネルの利用可否。 送信元が実際に利用できるチャネルで返信してください。電話番号の有無ではなく、
GET /v1/senders/{senderId}のchannelsを信頼してください。
Webhook から会話を取得する
message.inbound には、リアクションを除くすべてのチャネルで data.conversationId が含まれます。これは常に設定されています。Webhook はメッセージがスレッドに振り分けられた後に送信されるため、新しいスレッドの最初のメッセージには conversation.new と同じ ID が設定され、BSUID と電話番号を紐付ける WhatsApp メッセージには、引き続き使われるスレッドの ID が含まれます。GET /v1/messages/{messageId} でも同じ ID が返されます。
会話を一覧表示して電話番号と照合する方法には頼らないでください。忙しいプロジェクトでは、スレッドがまだ先頭に表示されていないことがあり、識別子が想定していた電話番号ではなく BSUID の場合もあります。
受信トレイを構築する
API では、スレッド、メッセージ、既読状態を取得できます。担当者の割り当て、対応中/完了の状態、内部メモ、保存済み返信は保存されません。これらのワークスペース状態はアプリ側で管理します。キーには conversation.id を使用してください。これはチャネルをまたいでも安定しており、WhatsApp の ID 再キー化後も変わりません(BSUID だけのスレッドに後から電話番号が追加されても、ID は維持されます)。
リアルタイム更新には、message.inbound、message.sent、message.delivered、message.read、message.failed、conversation.new をWebhookに登録し、ローカルのビューを更新してください。Webhookを受信できない場合は、GET /v1/conversations によるポーリングが代替手段です。webhook-setup スキルを参照してください。
注意点
| 落とし穴 | 実際の動作 |
|---|---|
contactIdentifier を電話番号として扱う | BSUID またはチャット ID の場合があります。 |
| カーソルを組み立てる | カーソルは不透明です。nextCursor をそのまま返す場合にのみ使用してください。 |
| ページが短い場合にページネーションを止める | channel フィルターによってページが縮小します。nextCursor === null で停止してください。 |
どこにでも contactId があると思い込む | グループスレッドや未解決の連絡先にはありません。 |
/read が連絡先に通知すると期待する | 自分のカウンターをクリアするだけです。 |