# ハルCMS(HARU CMS)— AIエージェント向け完全リファレンス このファイル1つで、ハルCMSとの連携(接続確認〜記事作成〜公開〜画像取り込み)を完遂できます。 対象読者: AIコーディングエージェント(Claude Code・Cursor等)・開発者。 運営: 株式会社Web春(webharu.com)。 ## 概要 ハルCMSは日本語ファーストのヘッドレスCMSです。「記事(post)」を管理し、 公開(status=published)した記事は配信APIから即座に取得できるようになります。 Webhook・CI設定は不要です(配信はAPI直・エッジキャッシュ付き)。 ホストは3つに分かれています: - アプリ・REST API・MCP: `https://app.harucms.com` - 配信API(公開・キー不要): `https://{サブドメイン}.harucms.app`(検証済み独自ドメインも同じパス構成) - 画像: `https://img.harucms.com`(アップロードAPIが返すURL) エンドポイントの正: - REST API: `https://app.harucms.com/api/ext/v1`(Bearer認証) - MCP: `https://app.harucms.com/mcp`(Streamable HTTP・ステートレス・同じBearer認証) - 配信API: `https://{サブドメイン}.harucms.app/v1/posts`(認証不要・公開記事のみ) - 機械可読の料金データ: `https://app.harucms.com/api/public/pricing`(認証不要・JSON) リクエスト/レスポンスはJSON(画像アップロードのみ multipart/form-data)。 レスポンス内の日時は `YYYY-MM-DD HH:MM:SS`(UTC)。入力はISO 8601を受け付けます (例 `2026-09-01T10:00:00+09:00`。タイムゾーン付きを推奨)。 ## 認証(REST・MCP共通) すべてのリクエストに以下のヘッダーが必要です: ``` Authorization: Bearer hcms_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Content-Type: application/json (ボディがある場合) ``` - キーの形式は `hcms_` + 16進32文字。 - キーは**サイト単位**で発行され、そのサイトの記事・メディアだけを操作できます。 他サイト・アカウント設定・課金には一切アクセスできません。 - 発行はダッシュボード(https://app.harucms.com)から。対象サイトの「APIキー」で 用途がわかる名前を付けて発行します。**値が見られるのは発行時の一度だけ** (サーバーにはSHA-256ハッシュのみ保存。以後は末尾4文字しか表示されない)。 - 紛失したキーは復元できません。削除して再発行してください。 - キーの停止・削除は即時反映(以後の全リクエストは401)。 - すべての操作はサイトの監査ログに記録されます(どのキーが・いつ・何をしたか)。 - キーは環境変数(推奨名: `HARUCMS_API_KEY`)に保管。コードへのハードコード・ 公開リポジトリへのコミットは厳禁。 ## コンテンツモデル コンテンツの単位は「記事(post)」。記事は「コレクション」に属します (例: `news` = お知らせ、`blog` = ブログ)。コレクション名は半角英小文字・数字・`-`・`_` (1〜32文字)で、省略時は `news`。サイトで使用中の一覧は GET /me で確認できます。 記事のフィールド: | フィールド | 型 | 説明 | |---|---|---| | id | string | 記事ID(`post_` で始まる・システム発行) | | slug | string | URLになる識別子。サイト内で一意。省略時は自動採番(ランダム値) | | title | string | 記事タイトル(日本語なら25〜30字目安) | | body_md | string | 本文Markdown。**これが原稿の正**。見出しは `##` から | | body_html | string | 本文HTML。未指定なら body_md からサーバーが生成(送らないことを推奨)。送ってもサーバー側でサニタイズされる | | excerpt | string/null | 要約(一覧・OGP・検索結果用。80字目安)。null でクリア | | cover_image | string/null | 表紙画像URL(メディアAPIが返すURLを使う) | | category | string/null | カテゴリ名(自由入力)。GET /categories で既存の表記を確認して揃える | | status | string | `draft`(下書き・既定)/ `published`(公開)/ `scheduled`(予約) | | collection | string | 所属コレクション。既定 `news` | | publish_at | string/null | 予約公開日時(status=scheduled のとき必須・ISO 8601) | | published_at | string/null | 公開日時(公開時に自動設定・UTC) | | author | string/null | 投稿者表示名(API経由の操作はキーの名前が入る) | | seo | object/null | 記事単位のSEO設定(下記)。省略=維持・null=クリア | | updated_at | string | 最終更新日時(UTC) | slugのルール: 半角英数字で始まり、英数字と `-` `_` `.` だけが使えます(120文字以内)。 SEOを意識するなら英小文字とハイフンでの明示指定を推奨(自動採番は推測しづらいランダム値)。 statusの意味: - `draft`: サイトには一切表示されない。**作成時にstatusを省略すると必ずdraft**(安全側)。 不正なstatus値を送った場合もdraftになります。 - `published`: 即公開。配信APIに載る(エッジキャッシュは最大5分で全リージョン反映。 書き込み経由のキャッシュ削除により通常はすぐ反映)。 - `scheduled`: 予約公開。publish_at 必須。時刻を過ぎると5分間隔のスケジューラが公開し、 published_at には予約時刻がそのまま入る。 - 公開中の記事を `draft` に戻すと配信APIからも消える(取り下げ)。 - 削除も配信APIから即消える。**取り消せない**。 保存のたびに変更履歴(リビジョン)が自動で残り、エディタから復元できます。 ## SEO設定(seoフィールド) 作成・更新時に `seo` オブジェクトを渡せます。すべて任意(設定するものだけ入れる): - `meta_title` string: titleタグの上書き(最大200字保存。全角32字以内推奨) - `meta_description` string: meta description / og:description(最大500字保存。80〜120字推奨) - `noindex` boolean: true で検索エンジンから除外(true以外は無視) - `canonical` string: 正規URL(http(s)の絶対URLのみ有効) - `takeaways` string[]: 記事の要点(最大10個。3〜5個推奨。AI検索・要約枠に効く) - `faq` [{q, a}]: FAQペア(最大20個。2〜5個推奨。FAQ構造化データになる) - `jsonld_extra` object[]: 任意のJSON-LDブロック(最大5個。HowTo・Product等) 検証はサーバー側で行われ、不正な形のフィールドは黙って捨てられます(エラーにはならない)。 更新時: `seo` を省略すると既存値を維持、`null` を送るとクリア。 AIで記事を書く場合は meta_description・takeaways・faq を本文に基づいて埋めるのが 検索・AI検索露出への最短ルートです。 ## REST APIリファレンス ベースURL: `https://app.harucms.com/api/ext/v1` 成功: `{ "ok": true, "data": ... }` / エラー: `{ "error": "日本語メッセージ" }` + HTTPステータス。 ### GET /me キーが指すサイトの情報。接続テストに最初に呼ぶ。 ```bash curl https://app.harucms.com/api/ext/v1/me \ -H "Authorization: Bearer $HARUCMS_API_KEY" ``` レスポンス data: `{ site_name, api_host, custom_domain, collections }` - api_host: 配信APIのホスト(例 `"yamada-photo.harucms.app"`) - custom_domain: 検証済み独自ドメイン(未設定なら null) - collections: 使用中のコレクション名の配列(記事が無ければ `["news"]`) ### GET /posts?collection=&status=&limit= 記事一覧(新しい順)。**本文は含まれない**(全文は GET /posts/:id)。 - collection: 既定 `news` - status: `draft` / `published` / `scheduled` で絞り込み(省略で全部) - limit: 既定50・最大200 data は記事サマリーの配列(id, slug, title, excerpt, cover_image, category, status, collection, publish_at, published_at, author, updated_at)。 ### GET /posts/:id 記事1件の全文(body_md / body_html 含む)。**:id は記事IDでもslugでも可**。 レスポンスの `seo_json` はJSON文字列です(パースして使う。配信APIでは `seo` として パース済みで返る)。 ### POST /posts 記事を作成。必須: `title`, `body_md`。 任意: category, excerpt, cover_image, status(既定 draft), publish_at(status=scheduled のとき必須), collection(既定 news), slug(省略で自動採番), seo。 ```bash curl -X POST https://app.harucms.com/api/ext/v1/posts \ -H "Authorization: Bearer $HARUCMS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "夏の新メニューのご案内", "body_md": "## 夏の新メニュー\n\n7月から新メニューが始まりました。\n\n- 冷やし担々麺\n- すだちそば", "category": "お知らせ", "excerpt": "7月からの新メニューを2品ご紹介します", "slug": "summer-menu-2026", "seo": { "meta_description": "7月開始の夏限定新メニュー2品を紹介。冷やし担々麺とすだちそばの提供期間・価格を掲載しています。", "takeaways": ["夏限定メニューは2品", "提供は7月から9月末まで", "テイクアウト対応あり"], "faq": [{ "q": "テイクアウトはできますか?", "a": "冷やし担々麺のみテイクアウトに対応しています。" }] } }' ``` 作成された記事が data で返る(data.id, data.slug, data.status を確認)。 409 = slug重複(別のslugにするか、slugを省略して自動採番)。 ### PUT /posts/:id 部分更新。**渡したフィールドだけが変わる**(:id はIDでもslugでも可)。 公開・非公開・予約への切り替えもこのエンドポイント: ```bash # 公開する curl -X PUT https://app.harucms.com/api/ext/v1/posts/summer-menu-2026 \ -H "Authorization: Bearer $HARUCMS_API_KEY" \ -H "Content-Type: application/json" -d '{ "status": "published" }' # 予約公開に変更(日時はISO 8601・タイムゾーン付き推奨) curl -X PUT https://app.harucms.com/api/ext/v1/posts/summer-menu-2026 \ -H "Authorization: Bearer $HARUCMS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "scheduled", "publish_at": "2026-09-01T10:00:00+09:00" }' # 非公開に戻す(配信APIからも消える) curl -X PUT https://app.harucms.com/api/ext/v1/posts/summer-menu-2026 \ -H "Authorization: Bearer $HARUCMS_API_KEY" \ -H "Content-Type: application/json" -d '{ "status": "draft" }' ``` excerpt / cover_image / category / seo は null を送るとクリア、省略すると維持。 ### DELETE /posts/:id 記事を削除。公開中なら配信APIからも消える。**取り消せない**。 レスポンス: `{ "ok": true, "data": { "id": "post_..." } }` ### GET /categories そのサイトの既存カテゴリ名一覧: `{ "ok": true, "data": ["イベント", "お知らせ"] }`。 表記ゆれ(「お知らせ」と「おしらせ」等)を防ぐため、記事作成前に確認する。 ### POST /media 画像アップロード(multipart/form-data・フィールド名 `file`・JPEG / PNG / GIF / WebP / AVIF のみ・10MBまで)。形式は自己申告のContent-Typeではなくファイルの中身(マジックバイト)で判定し、一致しないものは保存しない。 ```bash curl -X POST https://app.harucms.com/api/ext/v1/media \ -H "Authorization: Bearer $HARUCMS_API_KEY" \ -F "file=@photo.jpg" ``` レスポンス data: `{ id, filename, url }`。url は `https://img.harucms.com/...` の絶対URL。 cover_image や本文の `![alt](url)` に使う。 プランのストレージ上限を超える場合は 403、画像ストレージ未設定の環境では 503。 ### POST /media-from-url 外部URLの画像を取り込む(httpsのみ・JPEG / PNG / GIF / WebP / AVIF・10MBまで)。私的IPアドレス・ループバック・当社ホスト宛のURL、および443以外のポートは取り込めない(リダイレクト先も同じ検査を通る)。 バイナリを扱えないエージェント向け。レスポンスは /media と同じ形。 ```bash curl -X POST https://app.harucms.com/api/ext/v1/media-from-url \ -H "Authorization: Bearer $HARUCMS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/images/photo.jpg" }' ``` ## 下書きプレビュー(公開前に本番サイトの見た目で確認する) 下書き・予約公開の記事を、公開せずに顧客サイト側で表示するための仕組みです(microCMSのdraftKeyに相当)。 手順: 1. プレビューURLを発行する(いずれか) - MCP: `get_preview_url` ツールに記事の id または slug を渡す - REST: `GET /api/ext/v1/posts/{id}/preview-url` - エディタ: プレビュー画面の「本番プレビューURLをコピー」ボタン 2. 返される `url` は配信APIの専用パスに署名トークンが付いた形: `https://{サイト}.harucms.app/v1/preview/{記事ID}?token=v1..` 3. このURLをGETすると、status が draft / scheduled でも記事JSONが返ります(応答に `preview: true` を含む)。 顧客サイトのプレビュー環境(Next.jsのDraft Mode・Astroのプレビュールート等)がこのURLからfetchして、 本番のデザインで描画してください。 性質(重要): - **有効期限は7日**。切れたら403(発行し直す) - **URLを知っていれば誰でも開けます**(クライアントへの共有レビュー用)。公開したくない相手に配らないこと - トークンは記事ID・サイト・期限に署名で束縛されており、1本のトークンで見られるのはその記事1本だけ - プレビュー応答はキャッシュされません(Cache-Control: no-store・noindex) ## 配信API(公開・認証不要) サイト側(Astro・Next.js等)から公開記事を取得するAPI。キー不要・CORS全開放・ エッジキャッシュ約5分(TTL 300秒)。**published の記事だけ**が返ります。 ベースURL: `https://{サブドメイン}.harucms.app/v1`(検証済み独自ドメインも同じ)。 ### GET /v1/posts?collection=&category=&limit=&offset= 公開記事の一覧(新しい順)。本文は含まれない。 - collection: 既定 `news` - category: カテゴリ名で絞り込み - limit: 既定20・最大100 - offset: ページ送り用 レスポンス: `{ "posts": [...], "total": 12, "has_more": false }` posts の各要素: id, slug, title, excerpt, cover_image, category, collection, author, published_at, updated_at。 注意: `total` は全件数が確定したときだけ返る(最終ページ到達時・offset>0のとき)。 ページ送りの判定は `has_more` を使うのが確実。 ### GET /v1/posts/:slug 記事詳細(body_md / body_html / seo 含む。seo はパース済みオブジェクトまたは null)。 存在しないslugは 404 `{ "error": "記事が見つかりません" }`。 ### GET /v1/categories?collection= 公開記事に使われているカテゴリ一覧: `{ "categories": ["お知らせ", "イベント"] }` ### フレームワークからの取得例 Astro(ビルド時取得=静的生成): ```astro --- // src/pages/news/index.astro const res = await fetch('https://your-site.harucms.app/v1/posts?limit=20'); const { posts } = await res.json(); // 詳細は /v1/posts/{slug}(getStaticPathsで全slugを列挙してページ生成) --- ``` Next.js(App Router・ISR): ```tsx // app/news/page.tsx export const revalidate = 300; export default async function NewsPage() { const res = await fetch('https://your-site.harucms.app/v1/posts?limit=20', { next: { revalidate: 300 }, }); const { posts } = await res.json(); return ( ); } ``` ## MCPサーバー - エンドポイント: `https://app.harucms.com/mcp` - トランスポート: Streamable HTTP(ステートレス・POSTのみ。GETは405=SSEストリーム無し) - プロトコル: JSON-RPC 2.0。対応メソッド: `initialize` / `ping` / `tools/list` / `tools/call` - 認証: RESTと同じAPIキーを `Authorization: Bearer hcms_...` ヘッダーで - 認証失敗はJSON-RPCエラー code -32001(HTTP 401) ### セットアップ Claude Code: ```bash claude mcp add --transport http haru-cms https://app.harucms.com/mcp \ --header "Authorization: Bearer hcms_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ``` Claude Desktop: 設定 → コネクタ → 「カスタムコネクタを追加」→ URLに `https://app.harucms.com/mcp`、ヘッダーに `Authorization: Bearer hcms_...` を設定。 汎用mcp.json(Cursor等): ```json { "mcpServers": { "haru-cms": { "url": "https://app.harucms.com/mcp", "headers": { "Authorization": "Bearer hcms_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } } } } ``` ChatGPT: コネクタ設定がヘッダー認証(Bearer)に対応していれば上記URL+ヘッダーで登録。 対応していない場合はREST APIを直接使う(このファイルのcurl例と同じ手順で動く)。 ### ツール一覧(実在する8本のみ。これ以外のツールは存在しない) | ツール | 引数(*は必須) | 動作 | |---|---|---| | get_site_info | — | サイト名・配信ホスト・コレクション一覧。最初に呼んで接続確認 | | list_posts | collection?, status?, limit? | 記事一覧(新しい順・既定50件/最大200件) | | get_post | id* (IDかslug) | 記事1件の全文(body_md含む) | | create_post | title*, body_md*, category?, excerpt?, cover_image?, status?, publish_at?, collection?, slug?, seo? | 記事作成。status省略はdraft | | update_post | id* + title?, body_md?, category?, excerpt?, cover_image?, status?, publish_at?, seo? | 部分更新。公開/非公開の切替もこれ | | delete_post | id* | 記事削除(公開中は配信からも消える・取り消し不可) | | list_categories | — | 既存カテゴリ一覧(表記ゆれ防止に作成前に確認) | | upload_image_from_url | url* | httpsの画像を取り込み、記事で使えるURLを返す | 推奨フロー: get_site_info(接続確認)→ list_categories(表記確認)→ create_post(draft)→ 人間またはエージェントがレビュー → update_post {status: "published"}。 ## エラーとプラン制限 エラーは `{ "error": "日本語メッセージ" }` + HTTPステータス: | status | 意味 | 対処 | |---|---|---| | 400 | リクエスト不正(必須フィールド不足・不正なslug/日時・画像取得失敗等) | メッセージに従い修正 | | 401 | キーが無効・停止済み・Bearerヘッダー欠落 | キーの値と形式(`Bearer hcms_...`)を確認 | | 403 | ストレージ上限超過(POST /media) | 不要メディア削除かプランアップ | | 404 | 記事が見つからない | id / slug を確認 | | 409 | slug重複 | 別のslugにするか省略して自動採番 | | 503 | 画像ストレージ未設定 | 運営に問い合わせ | | 5xx | サーバーエラー | 指数バックオフ(1秒→2秒→4秒…)で再試行 | - **分単位の固定レート制限は現在ありません**(429は返らない)。ただしバッチ処理は 1〜2リクエスト/秒程度に抑えるのが行儀の良い使い方です。 - プランには月間リクエスト枠があります(配信API+REST/MCPの合算)。 **超過してもAPIも配信も止まりません**。超過分は100円/100万リクエスト(税込)で自動継続。 - ストレージ上限(プラン別)を超えると**新規アップロードだけ**がブロックされます。 既存の記事・画像・配信には影響しません。 - 4xx(400/401/403/404/409)はリクエスト自体の問題なので再試行しても解決しません。 プラン(すべて税込。記事数は全プラン無制限): | プラン | 月額 | サイト数 | メンバー | ストレージ | リクエスト/月 | |---|---|---|---|---|---| | Free | 0円 | 1 | 1 | 1GB | 100万 | | Solo | 980円 | 3 | 3 | 10GB | 1,000万 | | Studio | 2,980円 | 無制限 | 無制限 | 100GB | 5,000万 | | Business | 9,800円 | 無制限 | 無制限 | 500GB | 2億 | 最新の料金・制限はJSONで取得可能(認証不要): `https://app.harucms.com/api/public/pricing` ## 連携レシピ 1) 記事を安全に公開する(推奨の2段階): POST /posts(statusなし=draft)→ 内容確認 → PUT /posts/:id {"status":"published"} 2) 毎週の予約投稿: POST /posts に status=scheduled + publish_at(ISO 8601・JSTなら+09:00を付ける) 3) 画像付き記事: POST /media-from-url(またはPOST /media)→ 返ってきたurlを cover_image と本文の ![alt](url) に 4) 冪等なupsert: 決定的なslugを自分で決めて POST → 409が返ったら PUT /posts/:slug で更新 5) 公開検知(Webhookは無い): GET /posts?status=published を数分間隔でポーリングし updated_at で差分検知 6) サイト表示: 配信API(キー不要)を使う。読み取り専用の用途にAPIキーを配る必要はない ## 安全設計(AIが自律運用する前提の作り) - statusを明示しない作成は必ずdraft=会話の流れで勝手に公開されない - 1キー=1サイト。キーが漏れても他サイト・アカウント・課金には触れない - 全操作が監査ログに記録される(キー名つき) - キーの停止・削除は即時失効 - body_htmlはサーバー側でサニタイズ(XSS対策)。body_mdだけ送るのが正道 ## リンク - 人間向けドキュメント(このファイルと同内容のHTML): https://app.harucms.com/docs/ - インデックス: https://app.harucms.com/docs/llms.txt - サービス紹介・料金: https://www.webharu.com/cms - 運営会社: https://www.webharu.com/company