Webharu CMS 開発者ドキュメント
記事の作成・公開・管理をプログラムとAIから。日本語サイト運用のために作られた、AI最適化ヘッドレスCMSのAPI & MCPリファレンス。
REST APIとMCPサーバーの2つの入口から、同じ公開パイプラインに接続します。記事を published にすると、静的サイトへのビルド・デプロイまで自動で完了します。
Webharu CMSとは
Webharu CMSは、株式会社Web春が運営するサイト運用基盤のヘッドレスCMSです。ブラウザの記事エディタ(noteライクなWYSIWYG)に加えて、この開発者向けAPIとMCPサーバーを提供します。
- 公開まで全自動 — APIで
status: "published"にした瞬間、サイトのビルド・デプロイが走り、数分で本番URLに反映されます(Git連携テナント)。Webhookの組み立てやCI設定は不要です。 - Markdownファースト — 本文は
body_md(Markdown)だけ送ればOK。HTML変換はサーバー側で行います。 - AI最適化 — MCPサーバー同梱・llms.txt 提供・削除やレート等の安全設計込み。AIエージェントがそのまま運用に参加できます。
- エディタと同一パイプライン — API経由の操作もブラウザのエディタと同じ検証・履歴・監査ログを通ります。どちらで編集しても一貫します。
クイックスタート
APIキーを発行してから、最初の記事を公開するまでの最短手順です。
0. APIキーを発行する
マイページ(cms.webharu.com/my/)にログイン →「AIツール連携(APIキー)」→ 用途がわかる名前を入れて発行。 キーの値が表示されるのは発行時の一度だけです(サーバーにはハッシュのみ保存)。1サイトあたり5本まで発行でき、停止・削除も同じ画面からいつでもできます。
1. 接続を確認する
curl https://cms.webharu.com/api/ext/v1/me \
-H "Authorization: Bearer whk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"const BASE = "https://cms.webharu.com/api/ext/v1";
const KEY = process.env.WEBHARU_API_KEY;
const res = await fetch(`${BASE}/me`, {
headers: { Authorization: `Bearer ${KEY}` },
});
console.log(await res.json());import os, requests
BASE = "https://cms.webharu.com/api/ext/v1"
KEY = os.environ["WEBHARU_API_KEY"]
r = requests.get(f"{BASE}/me", headers={"Authorization": f"Bearer {KEY}"})
print(r.json())// レスポンス
{
"ok": true,
"data": {
"tenant_name": "あなたのサイト名",
"site_url": "https://example.com",
"collections": [{ "key": "news", "label": "お知らせ" }]
}
}
2. 下書きを作る
curl -X POST "$BASE/posts" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "夏季休業のお知らせ",
"body_md": "## 休業期間\n\n8月13日〜16日はお休みをいただきます。",
"category": "お知らせ"
}'
status を省略すると必ず下書き(draft)として作成されます。サイトには載りません。
3. 公開する
curl -X PUT "$BASE/posts/夏季休業記事のidまたはslug" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "published" }'
認証とAPIキー
すべてのリクエストに以下のヘッダーが必要です。
| ヘッダー | 値 | 必須 |
|---|---|---|
Authorization | Bearer whk_...(発行されたAPIキー) | はい |
Content-Type | application/json(ボディがある場合) | POST / PUT |
キーの性質
- キーはサイト(テナント)ごとに発行され、そのサイトのコンテンツだけを操作できます。
- サーバー側にはキーのハッシュのみ保存されます。値が見られるのは発行時の一度だけです。紛失した場合はマイページで削除して再発行してください。
- キーの停止・削除は即時に反映されます(停止後の全リクエストは
401)。 - すべての操作は監査ログに記録されます(いつ・どのキーが・何をしたか)。
コンテンツモデル
Webharu CMSのコンテンツは「記事(post)」です。記事は「コレクション」に属します(例: news = お知らせ、blog = ブログ)。使えるコレクションは GET /me で確認できます。
記事のフィールド
| フィールド | 型 | 説明 |
|---|---|---|
id | string | 記事ID(post_で始まる。システム発行) |
slug | string | URLになる識別子。省略時は自動採番。テナント内で一意 |
title | string | 記事タイトル |
body_md | string | 本文(Markdown)。これが原稿の正 |
body_html | string | 本文HTML。未指定なら body_md からサーバーが生成 |
excerpt | string | null | 要約(一覧・OGP・検索結果用。80字目安) |
cover_image | string | null | 表紙画像URL(画像APIの返すURL) |
category | string | null | カテゴリ名(自由入力。既存一覧と表記を揃えると◎) |
status | string | draft(下書き)/ published(公開)/ scheduled(予約) |
collection | string | 所属コレクション。既定 news |
publish_at | string | null | 予約公開日時(scheduled のとき必須・ISO 8601) |
published_at | string | null | 公開日時(公開時に自動設定) |
author | string | null | 投稿者表示名(API経由はキー名が入る) |
updated_at | string | 最終更新日時 |
公開ステータスの遷移
| status | 挙動 |
|---|---|
draft | 下書き。サイトには一切表示されない(既定値) |
published | 即時公開。サイトへの反映(ビルド・デプロイ)が自動で走る |
scheduled | 予約公開。publish_at の時刻を過ぎると5分以内に自動公開 |
公開中の記事を draft に戻すと、サイトからも取り下げられます。
/api/ext/v1/meキーが指すサイトの情報を返します。接続テストと、使えるコレクションの確認に使います。
curl "$BASE/me" -H "Authorization: Bearer $KEY"
// 200
{ "ok": true, "data": { "tenant_name": "サイト名", "site_url": "https://example.com",
"collections": [{ "key": "news", "label": "お知らせ" }] } }
/api/ext/v1/posts記事一覧を新しい順に返します(本文は含みません。全文は GET /posts/:id)。
| クエリ | 型 | 説明 | |
|---|---|---|---|
collection | string | 任意 | 既定 news |
status | string | 任意 | draft / published / scheduled で絞り込み |
limit | number | 任意 | 件数上限(既定50・最大200) |
curl "$BASE/posts?status=published&limit=10" -H "Authorization: Bearer $KEY"
// 200
{ "ok": true, "data": [
{ "id": "post_...", "slug": "summer-notice", "title": "夏季休業のお知らせ",
"excerpt": "...", "cover_image": null, "category": "お知らせ",
"status": "published", "collection": "news",
"publish_at": null, "published_at": "2026-07-30 06:38:19",
"author": "連携アプリ", "updated_at": "2026-07-30 06:38:19" }
] }
/api/ext/v1/posts/:id記事1件の全文(body_md / body_html 含む)を返します。:id には記事IDでもslugでも指定できます。
curl "$BASE/posts/summer-notice" -H "Authorization: Bearer $KEY"
/api/ext/v1/posts記事を作成します。
| ボディ | 型 | 説明 | |
|---|---|---|---|
title | string | 必須 | 記事タイトル(25〜30字目安) |
body_md | string | 必須 | 本文Markdown。見出しは ## から |
category | string | 任意 | カテゴリ名 |
excerpt | string | 任意 | 要約(80字目安) |
cover_image | string | 任意 | 表紙画像URL |
status | string | 任意 | 既定 draft |
publish_at | string | 任意 | scheduled のとき必須(例 2026-08-01T10:00:00+09:00) |
collection | string | 任意 | 既定 news |
slug | string | 任意 | 英小文字とハイフン。省略で自動採番 |
body_html | string | 任意 | 通常は送らない(サーバーが body_md から生成) |
curl -X POST "$BASE/posts" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"title": "新メニューのご案内",
"body_md": "## 夏の新メニュー\n\n7月から新メニューが始まりました。\n\n- 冷やし担々麺\n- すだちそば",
"category": "お知らせ",
"excerpt": "7月からの新メニューを2品ご紹介します"
}'const res = await fetch(`${BASE}/posts`, {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
title: "新メニューのご案内",
body_md: "## 夏の新メニュー\n\n7月から新メニューが始まりました。",
category: "お知らせ",
}),
});
const { data } = await res.json(); // data.id, data.slug, data.status === "draft"r = requests.post(f"{BASE}/posts",
headers={"Authorization": f"Bearer {KEY}"},
json={
"title": "新メニューのご案内",
"body_md": "## 夏の新メニュー\n\n7月から新メニューが始まりました。",
"category": "お知らせ",
})
post = r.json()["data"] # post["status"] == "draft"/api/ext/v1/posts/:id記事を部分更新します。渡したフィールドだけが変わります(:id はIDでもslugでも可)。公開・非公開の切り替えもこのエンドポイントです。
# 公開する
curl -X PUT "$BASE/posts/post_abc123" -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" -d '{ "status": "published" }'
# 明日の朝10時に予約公開へ変更
curl -X PUT "$BASE/posts/post_abc123" -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "scheduled", "publish_at": "2026-07-31T10:00:00+09:00" }'
/api/ext/v1/posts/:id記事を削除します。公開中の記事はサイトからも取り下げられます。取り消せません。
curl -X DELETE "$BASE/posts/post_abc123" -H "Authorization: Bearer $KEY"
// 200
{ "ok": true, "data": { "id": "post_abc123" } }
/api/ext/v1/categoriesそのサイトで使われている既存カテゴリの一覧です。表記ゆれ(「お知らせ」と「おしらせ」など)を防ぐため、記事作成前の確認を推奨します。
{ "ok": true, "data": ["イベント", "お知らせ", "キャンペーン"] }
/api/ext/v1/media画像をアップロードし、記事で使えるURLを返します。multipart/form-data の file フィールドで送信します(image/*・10MBまで)。
curl -X POST "$BASE/media" -H "Authorization: Bearer $KEY" \
-F "file=@./photo.jpg"
// 200
{ "ok": true, "data": { "id": "med_...", "filename": "photo.jpg",
"url": "https://cms.webharu.com/media/..." } }
返ってきた url を cover_image や本文の  に使います。
/api/ext/v1/media-from-url外部URLの画像を取り込みます(https・image/*・10MBまで)。ファイルを扱えない環境(AIエージェント等)向けです。
curl -X POST "$BASE/media-from-url" -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/images/photo.jpg" }'
エラーとレート制限
エラーは共通フォーマット { "error": "日本語のメッセージ" } で返ります。
| ステータス | 意味 | 対処 |
|---|---|---|
400 | リクエスト不正 | メッセージに従い修正(必須フィールド不足・不正な日時など) |
401 | 認証失敗 | キーの値・Bearer プレフィックス・キーが停止されていないか確認 |
404 | 記事が見つからない | id / slug を確認 |
409 | slug重複 | 別のslugにするか、slugを省略して自動採番 |
429 | レート制限 | 間隔を空けて再試行(下記) |
5xx | サーバーエラー | 時間を置いて再試行。続く場合はサイト担当者へ |
レート制限
APIキーごとに 120リクエスト/分 です。超過すると 429 が返ります。バッチ処理は1秒に1〜2リクエスト程度に抑えるのが安全です。
429 と 5xx は指数バックオフ(1秒→2秒→4秒…)での再試行を推奨します。4xx(429以外)はリクエスト自体の問題なので再試行しても解決しません。MCPサーバー
MCP(Model Context Protocol)は、AIアシスタントに外部ツールを安全に接続するための標準プロトコルです。Webharu CMSはリモートMCPサーバーを同梱しており、Claude・Cursor等のAIから記事の作成・公開を直接指示できます。コードを書く必要はありません。
| 項目 | 値 |
|---|---|
| エンドポイント | https://cms.webharu.com/mcp |
| トランスポート | Streamable HTTP(ステートレス) |
| 認証 | APIキー(RESTと同じ)を Authorization: Bearer ヘッダーで |
Claude Code(CLI)
claude mcp add --transport http webharu-cms \
https://cms.webharu.com/mcp \
--header "Authorization: Bearer whk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
登録後、Claudeに「サイトにお知らせを1本書いて下書きにして」のように話すだけで動きます。
Claude Desktop
設定 → コネクタ → 「カスタムコネクタを追加」で上記URLを入力し、ヘッダーに Authorization: Bearer whk_... を設定します。
Cursor / その他のMCPクライアント
// mcp.json(Cursor: .cursor/mcp.json)
{
"mcpServers": {
"webharu-cms": {
"url": "https://cms.webharu.com/mcp",
"headers": { "Authorization": "Bearer whk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }
}
}
}
MCPツールリファレンス
| ツール | 説明 | 主な引数 |
|---|---|---|
get_site_info | サイト情報・コレクション一覧(接続確認) | — |
list_posts | 記事一覧(新しい順) | collection status limit |
get_post | 記事1件の全文 | id(IDかslug) |
create_post | 記事作成(既定は下書き) | title body_md category status ほか |
update_post | 部分更新・公開/非公開の切替 | id +変更フィールド |
delete_post | 記事削除(公開中はサイトからも消える) | id |
list_categories | 既存カテゴリ一覧 | — |
upload_image_from_url | 外部URLの画像を取り込みURLを返す | url |
安全設計
- 既定は下書き —
statusを明示しない作成は必ず下書きになります。AIが会話の流れで勝手に公開することはありません。 - スコープはサイト単位 — キーは1サイト分の権限しか持ちません。他サイト・管理機能には一切アクセスできません。
- 全操作が監査ログに記録 — いつ・どのキーが・どの記事に何をしたかをサイト担当者が確認できます。
- 即時失効 — キーを停止すればその瞬間からすべての操作が拒否されます。
create_post(下書き)→内容確認→update_post で公開、の2段階を推奨します。AIエージェントで導入する
このドキュメントの全文はAIが読み込みやすいプレーンテキストでも提供しています。AIエージェント(Claude Code等)に以下を渡すだけで、連携コードの実装やMCPセットアップを自動で行えます。
https://cms.webharu.com/docs/llms-full.txt を読んで、
Webharu CMSとの連携をセットアップしてください。
APIキー: (発行されたキーを環境変数 WEBHARU_API_KEY に設定済み)
やりたいこと: (例)毎週月曜にブログ下書きを自動生成する
例えばこんな依頼がそのまま動きます:
- 「MCPで接続して、今公開中の記事一覧を見せて」
- 「この議事録を要約してお知らせ記事の下書きにして」
- 「予約投稿で来週月曜9時に公開される記事を作って」
- 「Webhookは無いので、RSS代わりに published 一覧をポーリングするスクリプトを書いて」
llms.txt
AI向けのドキュメントインデックスと全文を公開しています。
| ファイル | 内容 |
|---|---|
/docs/llms.txt | インデックス(このドキュメントの目次と要点) |
/docs/llms-full.txt | 全文(API仕様・MCP仕様・コード例のすべて)。AIにはこちらを読ませてください |
FAQ
Webhookはありますか?
ベータ版では未提供です。公開済み記事の検知は GET /posts?status=published のポーリング(数分間隔)で代替できます。要望が多ければ正式版で検討します。
記事の取得だけしたい(読み取り専用キー)
ベータ版のキーは読み書き両方の権限を持ちます。読み取り専用スコープは今後のロードマップにあります。取得だけの用途でも現状のキーをそのまま使えます。
body_htmlを直接送ってもいい?
可能ですが推奨しません。body_md だけ送る方が、エディタとの相互編集・将来のデザイン変更に強くなります。
予約公開の精度は?
5分間隔のスケジューラが処理するため、指定時刻から最大5分程度の遅延があります。
1つのキーを複数のツールで使い回してもいい?
動作はしますが、ツールごとにキーを分けることを推奨します(監査ログでの区別・漏えい時に該当ツールだけ止められるため)。マイページから5本まで発行できます。
料金は?
ベータ期間中、契約クライアントは追加費用なしで利用できます。
変更履歴
| 日付 | 内容 |
|---|---|
| 2026-08-06 | APIキーをマイページから自分で発行・停止・削除できるようになりました(1サイト5本まで) |
| 2026-07-30 | ベータ公開。REST API v1(記事CRUD・カテゴリ・画像)・MCPサーバー(8ツール)・llms.txt |