朝の定例処理でChatworkのメッセージを自動取得していたら、ある日チームから言われた。「未読バッジが消えてるんですけど、誰か読みました?」
誰も読んでいない。APIが読んだのだ。
GET /rooms/{room_id}/messages はデフォルトで取得したメッセージを「既読」にする。自動化スクリプトが朝6時に走って、人間が起きる前に全部既読になっていた。未読をアテにして仕事の優先順位を決めていた人にとっては、これは事故だ。
ここから「APIで既読を汚さずにメッセージを取得する方法」を調べ始めた。結論として、Chatwork APIには既読を制御する3つの手段がある。
既読制御の3つの手段
1. force パラメータ(GET /rooms/{room_id}/messages)
# force=0(デフォルト): 未読メッセージのみ取得 → 取得したら既読になる
curl -s -H "X-ChatWorkToken: $TOKEN" \
"https://api.chatwork.com/v2/rooms/${ROOM_ID}/messages"
# force=1: 既読含めて最新100件取得 → 既読状態を変えない
curl -s -H "X-ChatWorkToken: $TOKEN" \
"https://api.chatwork.com/v2/rooms/${ROOM_ID}/messages?force=1"
force=0 と force=1 は挙動が全く違う。
| パラメータ | 取得範囲 | 既読への影響 |
|---|---|---|
force=0(デフォルト) |
未読メッセージのみ | 取得すると既読になる |
force=1 |
最新100件(既読含む) | 既読状態を変えない |
正直、最初は「forceって何を強制してるんだ?」と思った。ドキュメントには「未読にかかわらず最新の100件を取得する」とだけ書いてある。既読を汚さないという副作用が自動化では一番重要なのに、そこは明記されていない。
2. PUT /rooms/{room_id}/messages/read — 既読にする
特定のメッセージまでを既読にするAPI。
# message_id を指定して、そこまでを既読にする
curl -s -X PUT \
-H "X-ChatWorkToken: $TOKEN" \
-d "message_id=12345" \
"https://api.chatwork.com/v2/rooms/${ROOM_ID}/messages/read"
レスポンス:
{
"unread_num": 0,
"mention_num": 0
}
message_id を省略すると、そのルームの全メッセージが既読になる。部分的に既読にしたいときは message_id を指定する。
3. PUT /rooms/{room_id}/messages/unread — 未読に戻す
既読にしたメッセージを未読に戻すAPI。これが地味に使える。
# message_id を指定して、そこから未読に戻す
curl -s -X PUT \
-H "X-ChatWorkToken: $TOKEN" \
-d "message_id=12345" \
"https://api.chatwork.com/v2/rooms/${ROOM_ID}/messages/unread"
レスポンス:
{
"unread_num": 3,
"mention_num": 1
}
指定した message_id 以降のメッセージが未読に戻る。
force=1 の5分制限という罠
force=1 は既読を汚さない便利なパラメータだが、レート制限が厳しい。公式ドキュメントには明記されていないが、実際に叩いてみると 5分に1回 程度の制限がかかる。
# 1回目: OK
curl -s -H "X-ChatWorkToken: $TOKEN" \
"https://api.chatwork.com/v2/rooms/${ROOM_ID}/messages?force=1"
# → 200 OK, メッセージ返る
# 直後にもう一回: NG
curl -s -H "X-ChatWorkToken: $TOKEN" \
"https://api.chatwork.com/v2/rooms/${ROOM_ID}/messages?force=1"
# → 200 OK だが空配列 []
ここが厄介なのは、429(Too Many Requests)が返るわけではないこと。ステータスコードは200で、ボディが空配列。エラーに見えない。「メッセージないのかな?」と誤解する。
複数ルームを順に取得するときは、ルーム間に5分以上のインターバルを入れるか、後述するread/unreadを組み合わせる方が現実的だ。
実践パターン: 自動取得で既読を汚さない
毎朝メッセージを吸い上げるスクリプトで、既読状態を保全するパターンは2つある。
パターンA: force=1 で取得(シンプル)
#!/bin/bash
TOKEN="$CHATWORK_API_TOKEN"
ROOM_ID="$TARGET_ROOM_ID"
# force=1 で既読を汚さず取得
messages=$(curl -s -H "X-ChatWorkToken: $TOKEN" \
"https://api.chatwork.com/v2/rooms/${ROOM_ID}/messages?force=1")
# 取得したメッセージを処理
echo "$messages" | python3 -c "
import json, sys
msgs = json.load(sys.stdin)
for m in msgs:
print(f\"[{m['account']['name']}] {m['body'][:80]}\")
"
1ルームだけならこれで十分。5分制限に引っかからない。
パターンB: force=0 + unread で復元(複数ルーム向け)
#!/bin/bash
TOKEN="$CHATWORK_API_TOKEN"
ROOM_IDS=("$ROOM_A" "$ROOM_B" "$ROOM_C")
for ROOM_ID in "${ROOM_IDS[@]}"; do
# force=0 で未読メッセージを取得(既読になる)
messages=$(curl -s -H "X-ChatWorkToken: $TOKEN" \
"https://api.chatwork.com/v2/rooms/${ROOM_ID}/messages")
if [ -z "$messages" ] || [ "$messages" = "[]" ]; then
continue
fi
# 最初のメッセージIDを取得
first_id=$(echo "$messages" | python3 -c "
import json, sys
msgs = json.load(sys.stdin)
if msgs:
print(msgs[0]['message_id'])
")
# 取得したメッセージを処理
echo "$messages" | python3 -c "
import json, sys
msgs = json.load(sys.stdin)
for m in msgs:
print(f\"[{m['account']['name']}] {m['body'][:80]}\")
"
# 未読に戻す
if [ -n "$first_id" ]; then
curl -s -X PUT \
-H "X-ChatWorkToken: $TOKEN" \
-d "message_id=${first_id}" \
"https://api.chatwork.com/v2/rooms/${ROOM_ID}/messages/unread" > /dev/null
fi
done
force=0 で取って即 unread で戻す。5分制限を気にせず複数ルームを連続処理できる。
ただし注意点がある。force=0 で取得した瞬間に既読になるので、unread を呼ぶまでの数秒間は既読状態になる。その間にChatworkアプリを開いていると、一瞬バッジが消えて戻るという挙動になる。実用上は問題ないが、気持ち悪いと感じる人もいるかもしれない。
read/unread APIの活用シーン
既読制御が役に立つ場面をいくつか。
1. 自動既読マーク: ボットが処理済みのメッセージを既読にする。人間が見なくていいメッセージ(ログ通知など)を自動で既読にして、重要なメッセージだけ未読で残す。
# ボット自身のメッセージだけ既読にする
last_bot_msg_id=$(echo "$messages" | python3 -c "
import json, sys
msgs = json.load(sys.stdin)
bot_msgs = [m for m in msgs if m['account']['account_id'] == BOT_ACCOUNT_ID]
if bot_msgs:
print(bot_msgs[-1]['message_id'])
")
curl -s -X PUT \
-H "X-ChatWorkToken: $TOKEN" \
-d "message_id=${last_bot_msg_id}" \
"https://api.chatwork.com/v2/rooms/${ROOM_ID}/messages/read"
2. 未読リマインダー: 特定のメッセージを未読に戻して、後で対応するためのリマインダーにする。
3. 監視系の読み取り: ルームのメッセージを監視するが、既読は人間の判断に任せたい。force=1 で覗くだけ。
まとめ
| 手段 | 用途 | レート制限 |
|---|---|---|
force=1 |
既読を汚さず取得 | 5分に1回 |
force=0 |
未読のみ取得(既読化する) | 通常のAPI制限 |
read API |
指定メッセージまで既読化 | 通常のAPI制限 |
unread API |
指定メッセージ以降を未読に戻す | 通常のAPI制限 |
自動化では force=1 を基本にして、複数ルームの連続取得が必要なら force=0 + unread を組み合わせる。この2パターンを押さえておけば、既読事故は起きない。
APIでメッセージを取得するだけなら5分で書ける。だが「既読を壊さない」という地味な要件を満たそうとすると、仕様の裏側を知る必要がある。ドキュメントに書いてあることと、実際に叩いて初めてわかることの差は、このAPIでは結構大きい。
Chatworkシリーズ
- #1 なぜ2026年にまだChatworkを使い倒しているのか
- #2 chatwork-client-gas、ぶっちゃけいるの?
- #3 ルームの参加者データだけで、組織の人間関係マップを作った
- #4 「Chatworkに確定連絡が来たら請求書を送る」をGASで自動化する
- #5 Chatwork MCPを繋いだら、17ルームの未読が10秒で片付いた
- #6 MCP vs GAS — Chatwork自動化の「正解」はどっちか
- #7 コンタクト承認をn8nで自動化しようとしたら、3つの罠にハマった
- #8 ChatworkにAIチームを住まわせたら、勝手に会話が始まった
- #9 Chatwork 8ルームの全メッセージからFAQ429件を自動抽出した
- #10 Webhook署名検証を入れたら全メッセージが消えた
- #11 過去メッセージを全件取得しようとしたら、APIの「100件の壁」にハマった
- #12 Chatwork APIの「既読」は自分で制御できる(この記事)
- #13 Chatwork APIのファイル機能、使ったことある?
- #14 n8nで全ルーム巡回
- #15 タスク機能をAPIで使い倒す
- #16 MCPを2アカウント同時接続したら、仕事用と事務局用が1画面で回った
- #17 【世界初かもしれない】ChatworkでClaude Code Channelsを実装してみた
- #18 Chatwork × Dify × GASで問い合わせ回答を自動提案する
- #19 RelationMapを夜間バッチで毎日自動更新する
- #20 17記事書いて見えた、Chatwork APIエコシステムに足りないもの
- #21 Googleフォームの回答をChatworkに自動投稿するGAS
- #22 Chatworkの会話を毎日AIが要約してくれる仕組みをn8nで作った話
- #23 chatwork-cliを入れたら、シェルからChatworkが操作できて世界が変わった
- #24 ChatworkのWebhookをn8nで受けるなら、HMAC署名検証は必ずやれ
- #25 Chatwork × GAS × Claude Codeで会員制講座の運用を自動化した