supacms Docs

API の使いかた

REST API を使って supacms のコンテンツを管理・取得する方法

ベース URL

https://your-supacms.com/api/v1

この文書は公開 REST API(/api/v1)だけを対象にします。Stripe からの通知(POST /api/stripe/webhook)は API キーでは呼べません。

認証

すべてのエンドポイントに Authorization ヘッダーが必要です。

Authorization: Bearer <api-key>

APIキーは管理画面の 設定 → APIキー から発行し、そのときに権限のプリセットを選択します。最初のキーは管理画面から発行してください。REST 経由の発行には apiKey:create を持つ既存キーが必要なためです。

有効な API キーで認証できた /api/v1 のリクエストは、そのキーが属する組織の当月の呼び出し回数に加算されます。期間は UTC の月初から月末です。計測の書き込みに失敗しても、API のレスポンスは変わりません。回数は管理画面の 設定 → 使用量 で確認できます。

権限(permission)

APIキーは permission の集合を持ちます。permission は管理APIのエンドポイントと1対1に対応し、宣言された permission を持たないキーでそのエンドポイントを呼ぶと 403 Forbidden が返ります。

公開コンテンツ取得のエンドポイントは permission を要求しません。permission を1つも持たないキーは公開コンテンツの取得のみに使えます。フロントエンドに埋め込むキーはこの形で発行してください。

permission

対応するエンドポイント

project:get

GET /projects/:projectSlug/

contentType:list

GET /projects/:projectSlug/content-types/

contentType:create

POST /projects/:projectSlug/content-types/

contentType:update

PATCH /projects/:projectSlug/content-types/:ctApiId

field:list

GET /projects/:projectSlug/content-types/:ctApiId/fields/

field:create

POST /projects/:projectSlug/content-types/:ctApiId/fields/

field:update

PATCH /projects/:projectSlug/content-types/:ctApiId/fields/:fieldApiId

entry:list

GET /projects/:projectSlug/content-types/:ctApiId/entries/

entry:create

POST /projects/:projectSlug/content-types/:ctApiId/entries/

entry:update

PATCH /projects/:projectSlug/content-types/:ctApiId/entries/:entryId

entry:delete

DELETE /projects/:projectSlug/content-types/:ctApiId/entries/:entryId

entry:publish

POST /projects/:projectSlug/content-types/:ctApiId/entries/:entryId/publish

entry:unpublish

POST /projects/:projectSlug/content-types/:ctApiId/entries/:entryId/unpublish

entry:updateSystem

PATCH /projects/:projectSlug/content-types/:ctApiId/entries/:entryId/system

entry:createDraftKey

POST /projects/:projectSlug/content-types/:ctApiId/entries/:entryId/draft-key

media:list

GET /projects/:projectSlug/media/

media:upload

POST /projects/:projectSlug/media/

apiKey:list

GET /projects/:projectSlug/api-keys/

apiKey:create

POST /projects/:projectSlug/api-keys/

プリセット

発行時に個別の permission を並べる代わりに、プリセットを指定できます。

プリセット

内容

配信用(プリセット指定なし)

permission を持たず、公開コンテンツ取得のみ

editor

コンテンツとメディアの操作一式。スキーマの変更とAPIキーの発行は含みません

admin

すべての permission

管理画面の発行フォームでは、この3つに加えて「カスタム」を選べます。カスタムでは permission をリソース別のチェックボックスで個別に選択します。REST では editor / admin を preset に指定するか、permissions に個別指定します。配信用はどちらも指定せずに発行します。

プロジェクトID

エンドポイントの :projectSlug は管理画面の 設定 → プロジェクト で確認・変更できます。変更すると API の URL も変わります。


公開コンテンツ取得(読み取り専用)

フロントエンドからコンテンツを読み取る用途のエンドポイントです。配信可能なエントリのみ返します。配信可能とは、公開済み(published)であり、かつ公開日時(publishedAt)が到来していることを指します。公開日時を未来にした「予約中」のエントリは、その時刻まで返りません。

レスポンスの Cache-Control は、同じプロジェクト内で次に公開予定のエントリがある場合、その時刻を跨がないよう短くなります。ただし予約を作る前に CDN へ載ったレスポンスには効かないため、公開時刻ちょうどの切り替わりは保証されません。直近数分の予約は反映が遅れることがあります。

エントリ一覧

GET /api/v1/projects/:projectSlug/:contentTypeApiId/

クエリパラメータ:

パラメータ

値

説明

limit

1〜100 の整数

取得件数の上限(省略時・最大とも 100)

offset

0 以上の整数

取得開始位置(省略時は 0)

filters

<fieldApiId>[equals|contains]<value>

フィールドによる絞り込み(下記参照)

orders

[-]publishedAt|createdAt|updatedAt

並び順(下記参照)

不正な limit / offset / orders を指定すると 400 エラーになります。limit が 100 を超える場合も 400 です。

totalCount は配信可能なエントリの総数です(予約中のエントリは数えません)。filters を指定した場合は絞り込み後の件数になります。limit / offset の影響は受けないので、ページネーションの総ページ数は Math.ceil(totalCount / limit) で計算できます。全件を取得する必要はありません。

filters パラメータ:

microCMS と同じく、一覧 API の filters でフィールド値を絞ります。単体取得(/:entryId)はエントリ ID 専用です。記事ページの URL が slug のときは、次のように 1 件に絞ります。

filters=<fieldApiId>[equals|contains]<value>
  • equals — short_text / long_text / url、および単一参照フィールド(multiple: false)

  • contains — 複数参照フィールド(multiple: true)

参照フィールドの値はエントリ ID です。

# slug が一致する公開済みの記事(1件に絞る)
GET /api/v1/projects/myblog/blogPost/?filters=slug[equals]hello-world&limit=1

# author フィールドが特定の著者エントリを参照している、公開済みの記事を取得
GET /api/v1/projects/myblog/blogPost/?filters=author[equals]01abc...

# tags フィールドに特定のタグを含む、公開済みの記事を取得
GET /api/v1/projects/myblog/blogPost/?filters=tags[contains]01def...

limit / offset によるページネーションも filters と組み合わせて使えます。このエンドポイントは常に配信可能なエントリのみを対象にするため、status パラメータはありません(管理APIの status パラメータとは異なります)。

orders パラメータ:

microCMS と同じく、orders で一覧の並び順を指定します。指定できるのは publishedAt(公開日時)・createdAt(作成日時)・updatedAt(更新日時)のいずれか1つです。先頭に - を付けると降順、付けないと昇順です。省略時は公開日時の新しい順です。

# 更新日時の新しい順
GET /api/v1/projects/myblog/blogPost/?orders=-updatedAt

# 作成日時の古い順に 10 件ずつ
GET /api/v1/projects/myblog/blogPost/?orders=createdAt&limit=10&offset=10

複数フィールドの指定(orders=-publishedAt,createdAt)には対応していません。400 を返します。同じ日時のエントリは、昇順ならエントリ ID の昇順、降順なら ID の降順で並びます。

レスポンス:

{
  "entries": [
    {
      "id": "01abc...",
      "fields": { "title": "記事タイトル" },
      "createdAt": "2024-01-01T00:00:00Z"
    }
  ],
  "totalCount": 123
}

エントリ取得

GET /api/v1/projects/:projectSlug/:contentTypeApiId/:entryId

:entryId はエントリの ID(UUID)です。slug では引けません。slug で探すときは一覧の filters=slug[equals]... を使います。

クエリパラメータ:

パラメータ

値

説明

draftKey

プレビューキー(後述)

下書きプレビューを取得(下記参照)

下書きプレビュー

draftKey を付けると、そのエントリに限り非公開の内容を取得できます。

GET /api/v1/projects/:projectSlug/:contentTypeApiId/:entryId?draftKey=<draft-key>
Authorization: Bearer <api-key>
  • 返すのは「最新の編集内容」です。未反映の編集版があればその内容、なければ確定版の内容を返します

  • 非公開(draft)のエントリも返します

  • 参照フィールド・リッチテキストの埋め込み・メディアは公開時と同じ範囲で展開します。参照先が非公開でも展開されます

  • レスポンスには Cache-Control: private, no-store が付きます。CDN やブラウザにキャッシュさせないでください

  • キーが不正・期限切れ・別エントリのものであれば 404 を返します。draftKey を付けないときの挙動は変わりません

  • キーはエントリごとに1つで、発行から24時間有効です

APIキー認証は従来どおり必要です。draftKey はAPIキーの代わりではなく、「この1件の下書きを見てよい」という追加の証明です。配信用キー(permission なし)でも draftKey があればプレビューを取得できます。

一覧取得はプレビューに対応していません。

プレビュー画面の作り方

編集者が管理画面の「画面プレビューを開く」を押すと、コンテンツごとに設定したプレビューURLが新しいタブで開きます。サイト側には、そのURLを受けて下書きを描画するページを1つ用意します。

  1. サイトにプレビュー用のページを作ります(例: https://example.com/preview)。クエリから id と draftKey を受け取ります

  2. ページのサーバー側で、受け取った値を付けて配信APIを呼びます。APIキーはブラウザに出さず、サーバー側で付けてください

GET /api/v1/projects/:projectSlug/:contentTypeApiId/<id>?draftKey=<draftKey>
Authorization: Bearer <api-key>
  1. 返ってきた内容を、本番の記事ページと同じコンポーネントで描画します。ページ自体も CDN にキャッシュさせないでください

  2. 管理画面のコンテンツ設計で、そのコンテンツの「プレビューURL」に次のように設定します

https://example.com/preview?id={entryId}&draftKey={draftKey}

{entryId} と {draftKey} は開くときに実際の値へ置き換わります。どちらも必須です。サイト側は {entryId} でどのエントリかを特定し、{draftKey} で下書きを取得します。{entryId} はパスに含めても構いません(例: https://example.com/blog/{entryId}/preview?draftKey={draftKey})。

プレビューで 404 が返るときは、次を確認してください。

  • draftKey の有効期限(発行から24時間)が切れていないか。管理画面で開き直すと、期限切れなら新しいキーで開きます

  • 管理画面で「URLを再発行」していないか。再発行すると、それまでのキーは使えなくなります

  • 呼び出している :contentTypeApiId と <id> が、プレビューURLのエントリと一致しているか


管理 API

スキーマやエントリを作成・更新するエンドポイントです。CLI と同等の操作が API から直接行えます。

ContentType

メソッド

パス

説明

GET

/projects/:projectSlug/content-types/

一覧取得

POST

/projects/:projectSlug/content-types/

新規作成

PATCH

/projects/:projectSlug/content-types/:ctApiId

更新

POST リクエストボディ:

{
  "name": "ブログ記事",
  "apiId": "blogPost"
}

apiId は英字始まり・英数字とアンダースコアのみ(例: blogPost, product_detail)。後から変更できます。

PATCH リクエストボディ:

{
  "name": "新しい表示名",
  "apiId": "blogPost",
  "previewUrlTemplate": "https://example.com/preview?id={entryId}&draftKey={draftKey}"
}

apiId を送ると変更できます。省略すると現在の値を保ちます。既に使われている値へ変えると 409 です。

previewUrlTemplate だけは扱いが異なり、省略すると現在の設定を保ちます。消したいときは null を明示してください。名前の変更だけを送ったときにプレビュー設定が消えないようにするためです。新しく設定する値、または保存済みと違う値は、http(s) で始まり {entryId} と {draftKey} の両方を含む必要があります。満たさない場合は 422 を返します。保存済みと同じ値を送り直す場合は、その値が今の規則に合わなくても受け付けます。

設定すると、エントリ編集画面の「画面プレビュー」から、{entryId} と {draftKey} を展開したURLを新しいタブで開けます。開く前に未保存の変更を保存するため、プレビューには直前の編集まで含まれます。URLのコピーと再発行も同じ場所から行えます。未設定のときは、コンテンツ設計を変更できる人にだけ設定ボタンが出ます。サイト側の実装はプレビュー画面の作り方を参照してください。


フィールド

メソッド

パス

説明

GET

/projects/:projectSlug/content-types/:ctApiId/fields/

一覧取得

POST

/projects/:projectSlug/content-types/:ctApiId/fields/

追加

PATCH

/projects/:projectSlug/content-types/:ctApiId/fields/:fieldApiId

更新

POST リクエストボディ:

{
  "name": "タイトル",
  "apiId": "title",
  "required": true,
  "config": { "maxLength": 200 }
}

PATCH リクエストボディ:

{
  "name": "新しい表示名",
  "apiId": "headline",
  "required": false,
  "config": { "maxLength": 300 }
}

apiId を変えると、既存エントリの data キーと参照も合わせて変わります。省略すると現在の値のままです。変更先のキーが既存エントリや更新履歴に残っている場合は 409 です。フィールドタイプと config の詳細は フィールドタイプ一覧 を参照してください。


エントリ

メソッド

パス

説明

GET

/projects/:projectSlug/content-types/:ctApiId/entries/

一覧取得

POST

/projects/:projectSlug/content-types/:ctApiId/entries/

作成(常に draft)

PATCH

/projects/:projectSlug/content-types/:ctApiId/entries/:entryId

カスタムフィールドの更新

DELETE

/projects/:projectSlug/content-types/:ctApiId/entries/:entryId

削除

POST

/projects/:projectSlug/content-types/:ctApiId/entries/:entryId/publish

公開(draft → published)/予約公開/編集版の反映

POST

/projects/:projectSlug/content-types/:ctApiId/entries/:entryId/unpublish

非公開化(published → draft)

PATCH

/projects/:projectSlug/content-types/:ctApiId/entries/:entryId/system

システム項目の更新(publishedAt)

POST

/projects/:projectSlug/content-types/:ctApiId/entries/:entryId/draft-key

プレビューキーの発行

GET クエリパラメータ:

パラメータ

値

説明

status

draft / published

ステータスでフィルタ(省略時は全件)

limit

1〜100 の整数

取得件数の上限(省略時・最大とも 100)

offset

0 以上の整数

取得開始位置(省略時は 0)

filters

<fieldApiId>[equals|contains]<value>

フィールドによる絞り込み(下記参照)

orders

[-]publishedAt|createdAt|updatedAt

並び順(下記参照)

不正な limit / offset / orders を指定すると 400 エラーになります。limit が 100 を超える場合も 400 です。

レスポンスは { "entries": [...], "totalCount": 123 } です。totalCount は status に一致するエントリの総数(省略時は非公開を含む全件)で、limit / offset / filters の影響を受けません。

filters パラメータ:

配信 API と同じ構文です。short_text / long_text / url は equals、参照は単一が equals・複数が contains です。

filters=<fieldApiId>[equals|contains]<value>
GET /projects/myblog/content-types/blogPost/entries/?filters=slug[equals]hello-world
GET /projects/myblog/content-types/blogPost/entries/?filters=author[equals]01abc...
GET /projects/myblog/content-types/blogPost/entries/?filters=tags[contains]01def...

filters と status は組み合わせて使用できます。limit / offset によるページネーションも適用されます。totalCount は status のみを反映し、filters では絞りません。

orders は配信 API と同じ構文です(例: ?orders=-updatedAt)。省略時は非公開のエントリを先頭に、公開日時の新しい順です。

POST / PATCH リクエストボディ:

{
  "data": {
    "title": "記事タイトル",
    "body": "本文テキスト"
  }
}

エントリは常に draft で作成されます。/publish で公開状態に変更してください。公開日時は実行時刻が入ります。

プランに加入していない組織のお試しでは、エントリは3件、画像は5枚までです。上限を超える作成・アップロード、および /publish は 403 を返します。error はそれぞれ「お試しではコンテンツを3件まで作成できます」「お試しでは画像を5枚までアップロードできます」「公開するにはプランへの加入が必要です」です。

予約公開:

/publish のボディに未来の publishedAt を渡すと、その時刻まで配信されない「予約中」のエントリになります。

{ "publishedAt": "2026-10-01T09:00:00+09:00" }

ボディを省略すればこれまでどおり即時公開です。予約中のエントリは status が published になりますが、公開日時が到来するまで公開コンテンツ取得には現れません。予約を取り消すには /unpublish で非公開に戻します。

/publish で受け付ける publishedAt は未来の日時だけです。過去の日時は 422 になります。すでに配信中のエントリの公開日時を変える(過去にさかのぼる)場合は PATCH /entries/:entryId/system を使ってください。予約中のエントリであれば、/publish に別の未来日時を送って予約日時を変更できます。publishedAt を省略して送れば、その場で即時公開に切り替わります。

予約中のエントリを PATCH で編集すると、変更は編集版として保存されます。予約時刻に公開されるのは編集版ではなく確定版の内容です。 予約公開に変更を含めるには、予約時刻より前に、publishedAt に現在の予約日時と同じ値を指定して /publish を送ってください。予約日時は保たれたまま、編集版が確定版へ反映されます。

{ "publishedAt": "2026-10-01T09:00:00+09:00" }

このとき publishedAt を省略すると即時公開になり、予約が消費されます。

entry.publish webhook は予約を登録した時点ではなく、公開日時が到来した時点で送信されます(最大1分の遅れがあります)。

プレビューキーの発行:

POST /projects/:projectSlug/content-types/:ctApiId/entries/:entryId/draft-key
{
  "draftKey": "6f1c2d3e-4b5a-4c6d-9e8f-7a6b5c4d3e2f",
  "expiresAt": "2026-10-17T05:00:00.000Z",
  "expiresInSeconds": 86400,
  "previewUrl": "https://example.com/preview?id=0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b&draftKey=6f1c2d3e-4b5a-4c6d-9e8f-7a6b5c4d3e2f"
}

フィールド

説明

draftKey

プレビューキー。?draftKey= に渡します

expiresAt

キーの有効期限(ISO 8601)

expiresInSeconds

有効期限までの残り秒数。既存のキーを返したときは24時間より短くなります

previewUrl

コンテンツのプレビューURLにキーを展開したURL。プレビューURLが未設定のコンテンツでは null

発行したキーは24時間有効で、そのエントリのプレビュー(?draftKey=)にのみ使えます。entry:createDraftKey が必要です。

有効なキーが既にあるときは同じ値と同じ有効期限を返します。共有済みのプレビューURLを壊さないためです。渡したURLを止めたいときは ?rotate=true を付けて呼び出してください(管理画面の「URLを再発行」と同じです)。新しいキーに差し替わり、それまでのキーは通らなくなります。

POST /projects/:projectSlug/content-types/:ctApiId/entries/:entryId/draft-key?rotate=true

published のエントリへの PATCH は、変更を編集版として保存し、公開コンテンツ取得の出力を変えません。配信へ反映するには /publish を送ってください。PATCH のレスポンスは配信中の内容(確定版)を返すため、送った値がそのまま返るわけではありません。draft のエントリはこれまでどおり即座に更新されます。

編集版を持つ published のエントリへの /publish は、編集版を entry.data へ反映します(publishedAt は初回公開時の値のまま)。編集版がない published のエントリに /publish を送ると 409 エラーになります。/unpublish も編集版があれば反映してから非公開にします。

編集版の反映では確定版と同じ検証を行うため、unique フィールドの重複は 409、本文の埋め込みが循環参照になる場合は 422 を返します。検証に失敗した反映は何も書き込みません。反映の直前に同じエントリの編集版が更新された場合も 409 を返すので、送り直してください。

PATCH も、同時に /unpublish が完了してエントリが draft になった場合は 409 を返します。送り直すと非公開エントリへの直接更新として扱われます。

システム側の publishedAt(エントリ直下。data のカスタムフィールドではない)を任意の日時にする場合は、公開したあとに PATCH /entries/:entryId/system を使います。非公開エントリには設定できず 422 になります。既存の editor キーには entry:updateSystem が無いため、使うにはキーの再発行が必要です。予約公開のように未来の日時を設定するだけなら /publish のボディで足りるので、entry:updateSystem は不要です。

PATCH /entries/:entryId/system で公開日時を未来に動かすと、そのエントリは公開コンテンツ取得から消えます。このとき飛ぶのは entry.update で、entry.unpublish は飛びません。entry.update を受けて再取得したら 404 になる場合があるのはこのためです。

PATCH /entries/:entryId/system リクエストボディ:

{ "publishedAt": "2020-01-15T10:00:00.000Z" }

移行時の手順は「作成 → 公開 → システム項目の更新」です。

POST /projects/myblog/content-types/blogPost/entries/
{ "data": { "title": "移行した記事" } }

POST /projects/myblog/content-types/blogPost/entries/:entryId/publish

PATCH /projects/myblog/content-types/blogPost/entries/:entryId/system
{ "publishedAt": "2020-01-15T10:00:00.000Z" }

APIキー

メソッド

パス

説明

GET

/projects/:projectSlug/api-keys/

一覧取得

POST

/projects/:projectSlug/api-keys/

新規作成

POST リクエストボディ:

{ "name": "プロダクション用", "preset": "editor" }

preset の代わりに permissions で個別に指定できます。両方を同時に指定すると 400 が返ります。不正な値を渡した場合の 400 には、どのフィールドが不正で何が許容されるかを示す detail が含まれます。

{ "name": "配信用", "permissions": ["project:get"] }

どちらも省略すると permission なし(公開コンテンツ取得のみ可)のキーになります。

発行できる permission は発行元キーの permission の範囲内に限られます。範囲を超える指定は 403 が返ります。

APIキーの値はレスポンスの key フィールドに含まれます。作成時のレスポンスにしか含まれません。安全な場所に保管してください。


メディア

メソッド

パス

説明

GET

/projects/:projectSlug/media/

一覧取得

POST

/projects/:projectSlug/media/

アップロード

POST リクエスト: multipart/form-data 形式

フィールド

必須

説明

file

✓

ファイル本体(JPEG / PNG / GIF / WebP / SVG、最大 10MB)

alt

-

代替テキスト(最大 500 文字)

compress

-

true / false。幅 1920px 超の JPEG / PNG / WebP を縮小する(既定: true)

webp

-

true / false。JPEG / PNG を WebP に変換する(既定: true)

レスポンス例:

既定では JPEG / PNG / WebP を最大幅 1920px に縮小し WebP に変換します。GIF と SVG は変換しません。compress=false や webp=false でそれぞれの処理をスキップできます。

{
  "id": "01938f...",
  "filename": "hero.webp",
  "mimeType": "image/webp",
  "size": 204800,
  "url": "https://cms.spcdn.dev/media/myproject/01938f...",
  "alt": "ヒーロー画像",
  "createdAt": "2026-08-05T09:00:00.000Z"
}

フィールド型のレスポンス形式

エントリの data フィールドには、ContentType に定義されたフィールドが含まれます。 各フィールドの型によって返す JSON 形式が異なります。

型

レスポンス形式

short_text / long_text

"文字列"

boolean

true / false

integer

42

decimal

3.14

datetime

"2026-08-06T05:30:09.078Z"

url

"https://example.com"

rich_text

{ "type": "doc", "content": [...] } (Tiptap JSON)。本文に埋め込まれた entryRef ノードは展開される(下記参照)

json

任意の JSON 値

media

{ "id": "...", "url": "https://cms.spcdn.dev/<r2Key>", "alt": "...", "size": 101108, "filename": "image.webp", "mimeType": "image/webp" }

media_list

media と同じ形式のオブジェクトの配列。編集画面で指定した順序がそのまま返ります

select (single)

"draft"

select (multiple)

["news", "blog"]

reference (single)

参照先エントリオブジェクト(配信可能なもののみ)、参照先が draft / 予約中 / 削除済みの場合は null

reference (multiple)

参照先エントリオブジェクトの配列(配信可能なもののみ。draft / 予約中 / 削除済みは配列から除外されるため、要素数が変わります)

reference 型のレスポンス例:

{
  "id": "01abc...",
  "data": {
    "title": "記事タイトル",
    "author": {
      "id": "01def...",
      "contentTypeId": "...",
      "data": { "name": "田中太郎" },
      "status": "published",
      "publishedAt": "2026-08-01T00:00:00.000Z",
      "createdAt": "2026-08-01T00:00:00.000Z",
      "updatedAt": "2026-08-01T00:00:00.000Z"
    },
    "tags": [
      { "id": "01ghi...", "data": { "name": "TypeScript" }, ... },
      { "id": "01jkl...", "data": { "name": "Effect" }, ... }
    ]
  }
}

参照展開は 1階層のみです。参照先エントリのさらに参照先は展開されません。

rich_text 内のエントリ埋め込み(entryRef)

rich_text の本文には、別のエントリへの参照を entryRef ノードとして埋め込めます。CTA ボタンや著者紹介などの共通パーツを1箇所で管理し、参照先を直すだけで参照している全記事の配信に反映させるための仕組みです。

配信時、entryRef ノードの attrs.data に参照先エントリが展開されます。

{
  "type": "entryRef",
  "attrs": {
    "entryId": "01abc...",
    "data": {
      "id": "01abc...",
      "contentTypeId": "...",
      "data": { "label": "無料相談はこちら", "href": "https://example.com/contact" },
      "status": "published",
      "publishedAt": "2026-08-01T00:00:00.000Z",
      "createdAt": "2026-08-01T00:00:00.000Z",
      "updatedAt": "2026-08-01T00:00:00.000Z"
    }
  }
}

attrs.data が null になるのは次の場合です。

  • 参照先エントリが draft(非公開)

  • 参照先エントリが予約中(公開日時が未到来)

  • 参照先エントリが削除済み

  • 参照先エントリの本文に含まれる entryRef(展開は reference 型と同じく 1階層のみ)

配信先では attrs.data が falsy なら描画しない、という分岐を1本書けば足ります。

case "entryRef":
  if (!node.attrs.data) return null;
  return <CtaButton {...node.attrs.data.data} />;

entryRef ノードの見た目は CMS 側では持ちません。配信先の JSON レンダラに分岐を追加しないと、エラーにならず何も表示されない点に注意してください。

なお、本文に埋め込まれているエントリは削除できません(422 が返ります)。取り下げたい場合は非公開にしてください。非公開にすると attrs.data が null になり、参照している全記事から一斉に消えます。

media 型の注意

media 型の url は 絶対 URL(CDN) で返ります。そのまま <img src> 等に使用できます。

const media = data.coverImage as { url: string; alt: string | null };
const imgUrl = media.url;
// 例: "https://cms.spcdn.dev/media/myproject/01abc..."

media_list 型の注意

media_list 型は同じ形式のオブジェクトを 配列 で返します。配列の順序は編集画面で並び替えた順序そのものなので、そのままギャラリーやスライダーの表示順として使えます。

const gallery = data.gallery as { url: string; alt: string | null }[];
gallery.map((image) => image.url);

optionalString(data.coverImage) のように文字列として扱うと [object Object] になります。 必ずオブジェクトとして処理してください。

// NG
const imgUrl = data.coverImage as string;

// OK
const media = data.coverImage as { url: string; alt: string | null };
const imgUrl = media.url;

選択した項目の変更日時

日時フィールドの config で自動記録を有効にし、変更を検知する項目の UUID を指定します。対象の UUID はフィールド一覧の id です。任意フィールドとして作成してください。

{
  "type": "datetime",
  "includeTime": true,
  "trackContentChanges": true,
  "trackedFieldIds": ["01994e2e-1b7e-7000-8000-000000000000"]
}

例の UUID は実際の対象項目の id に置き換えてください。apiId や項目名から対象を推測することはありません。UUID で指定するため、対象項目の apiId を改名しても設定は維持されます。

trackContentChanges の初期値は OFF です。未設定・false の日時は手入力・保存形式・履歴とも従来どおりです。他の組織やコンテンツへ自動適用されることはありません。管理画面では「選択した項目の変更時に日時を記録する」を ON にし、検知対象を選びます。

同じコンテンツにある、参照と自動記録日時以外の項目を1つ以上指定してください。対象未指定・重複・他コンテンツの項目・自分自身の指定は、公開や変更の確定時に設定エラーになります。対象項目を削除した場合や、参照・自動記録日時へ変更した場合も、検知対象を選び直してください。フィールドの追加・更新・削除の操作自体は従来どおりです。

値は項目全体で比較します。JSON オブジェクトのキー順は無視し、配列の順序や画像の説明文は比較します。HTML は文字列として扱うため、CSS や共通ナビゲーションの変更も対象です。HTML の本文抽出や特定の項目名への優先順位はありません。複数の日時フィールドを設定した場合、それぞれが選んだ対象を比較します。

公開中の記事では、下書き保存だけでは公開版の日時を変えず、変更を公開した時点で記録します。下書きを伴う公開停止でも変更を確定して記録します。確定履歴のある非公開記事への変更も記録します。初回の確定前の編集や取り込みでは、明示された日時を保持します。過去の日時を推測・補完する機能ではありません。

自動記録日時は時刻を含み、編集画面では確定済みの値を読み取り専用で表示します。対象に変更がなければ、API で別の日時を送っても既存値を保持します。既存値が不明なら空のままです。配信 API のシステム項目 updatedAt は保存日時のままなので、サイトの更新日には設定した data 内の日時を使ってください。

旧設定からの移行: trackContentChanges: true だけの設定には対象項目の選択が必要です。設定が不足している間は、公開・下書きを反映する公開停止・確定履歴のある非公開記事の更新が 422 で失敗し、確定済みデータを保持します。公開中の記事の下書き保存と設定の編集は可能です。OFF の日時には移行作業は不要です。

エントリ作成・更新時のバリデーション

エントリの data はフィールド定義に照らして検証されます。違反した場合は 422 を返します。

型

受け付ける値

フィールド設定による制約

short_text / long_text

文字列

maxLength 以内

integer

整数

min 以上 / max 以下

decimal

数値

min 以上 / max 以下

boolean

true / false

-

datetime

日時としてパースできる文字列

-

url

絶対 URL の文字列

-

media

url を持つオブジェクト

-

select

定義された選択肢の文字列(multiple: true の場合は文字列配列)

options に含まれる値

rich_text

{ "type": "doc", "content": [...] }(Tiptap JSON)

-

json

任意の JSON 値

-

必須フィールドは、キーが無い場合に加えて null や空白のみの文字列もエラーになります。 必須でないフィールドは値が無ければ検証をスキップします。

rich_text に Markdown やプレーンテキストの文字列を渡すと 422 になります。レスポンスと同じ Tiptap JSON のオブジェクトを渡してください。

テキストの一部を装飾する場合は、テキストノードの marks 配列を使います。対応する mark は bold / italic / underline / strike / highlight / code / link(attrs.href)です。

{
  "type": "paragraph",
  "content": [
    { "type": "text", "text": "通常の文字と" },
    { "type": "text", "text": "強調", "marks": [{ "type": "bold" }, { "type": "highlight" }] }
  ]
}

ユニーク制約

short_text フィールドの設定に unique: true を指定すると、同じコンテンツタイプ内で値の重複を禁止できます。

{ "type": "short_text", "unique": true }

重複する値でエントリを作成・更新した場合、および重複したままのエントリを /publish した場合は 409 を返します。レスポンスの error には衝突したエントリの ID が含まれます。

  • 検証範囲は同じコンテンツタイプ内のみです。別のコンテンツタイプであれば同じ値を持てます。

  • 空文字と値なしは重複として扱いません。

  • 既に重複している既存データは、その値を変更しない限り更新をブロックしません。ただし公開はできません。

部分更新と既存データの扱い

エントリ更新(PATCH)で値の検証対象になるのは、そのリクエストの data に含まれるキーだけです。必須フィールドの存在チェックだけは既存の値とマージした結果に対して行われるため、必須フィールドを null で消す更新は引き続きエラーになります。

この仕様により、検証が入る前に不正な値が保存されたエントリでも、そのフィールドを送らなければ他のフィールドだけを更新できます。rich_text に文字列が保存されているエントリは管理画面ではエディタの代わりに警告と生の値が表示されるため、正しい Tiptap JSON を PATCH で送って復旧させてください。


MCP エンドポイント

AI ツール(Claude 等)から SupaCMS を操作するための MCP (Model Context Protocol) エンドポイントです。

接続情報

https://cms.supa.ai/api/mcp

認証: OAuth(SupaCMS アカウントでサインイン)。API キーは不要です。

レート制限は REST API と同じです。詳細は レート制限 を参照してください。

利用できるツールの一覧・接続方法・移行手順は MCP サーバー導入ガイド を参照してください。


フロントエンド実装前の確認手順

フロントエンドから API を叩く前に、以下の手順で情報を確認してください。 これにより、URLの誤り・フィールド型の誤解・APIキーの問題を事前に発見できます。

# 1. プロジェクトのAPIベースURLを確認
supacms project info --project <your-project-slug> --api-key <your-api-key> --pretty

# 2. コンテンツタイプの apiId を確認
supacms content-type list --project <your-project-slug> --api-key <your-api-key> --pretty

# 3. フィールド定義を確認(型に注意)
supacms field list \
  --project <your-project-slug> \
  --content-type <contentTypeApiId> \
  --api-key <your-api-key> \
  --pretty

# 4. 実際にAPIを叩いてレスポンス形式を確認
curl https://<apiBaseUrl>/api/v1/projects/<projectSlug>/<contentTypeApiId>/ \
  -H "Authorization: Bearer <api-key>" | jq '.entries[0].data'

media 型フィールドがある場合は、文字列ではなくオブジェクトが返ります。 上記の jq コマンドで実際のレスポンスを確認してから実装してください。


エラーレスポンス

{ "error": "エラーメッセージ" }

ステータス

説明

400

リクエストボディが不正

401

APIキーが無効

403

プロジェクトへのアクセス権がない

404

リソースが見つからない

409

apiId の重複 / 不正なステータス遷移 / ユニークフィールドの値の重複

422

エントリのバリデーションエラー(必須フィールド不足、型不一致、フィールド設定違反)

429

レート制限を超過(下記参照)

500

内部サーバーエラー

レート制限

/api/v1・/api/mcp・/api/upload は、APIキー(Authorization: Bearer)あたり 10 秒間に 100 リクエスト までです。Bearer トークンが無いリクエストはクライアント IP あたり同じ上限です。

上限を超えると 429 と Retry-After: 10 が返ります。

{ "error": "Too Many Requests" }

認証(/api/auth)とメディア配信(/api/media)にはこの制限はかかりません。

画像の配信情報とサイズ別配信

メディア一覧の各項目には metadata(caption / width / height)が含まれます。既存データの初期値は空のオブジェクトです。JPEG・PNG・WebPを変換してアップロードした場合や、管理画面で縮小した場合は実際の寸法を保存します。

公開コンテンツの画像フィールドとリッチテキストの画像は、同じプロジェクトの最新のメディア情報を展開します。リッチテキストは attrs.mediaId、旧記事は登録済み画像と一致するURLで照合します。記事ごとの altSource: "custom" は空文字も含めて優先し、altSource: "library" は共通の代替テキストに追従します。旧データの非空altは個別指定として保持します。記事の保存データそのものは書き換えません。

寸法があるJPEG・PNG・WebPには srcset と sizes を付けます。寸法が不明な旧画像では省略します。静的サイトへの変更反映には再ビルドが必要です。

GET /api/media/:r2Key?width=640

画像ファイルの配信は既存の公開URLと同じく認証不要です。width は 320 / 640 / 960 / 1280 / 1600 のいずれか。縦横比を維持し、元画像を拡大せずWebPへ変換します。未対応の幅は400、画像がなければ404です。GIF・SVGは元ファイルを返します。変換結果は別のオブジェクトとして保存し、以後は保存済みの結果を返します。サイズ変更は新しい画像URLを発行するため、既存のimmutableキャッシュと衝突しません。元画像と旧URLは保持します。

記事の公開前チェック

slug と body / bodyHtml / legacyDocument を持つ記事は、公開時にURLの重複、AIチャットの共有URLが画像やリンクへ残っていないか、本文に未変換のMarkdown記号がないかを確認します。共有URLは ChatGPT・Claude・Gemini の共有ページが対象で、サービスの通常URLは許可します。コードブロック・インラインコード内の記号は対象外です。問題がある場合は公開を止め、下書きと公開中の記事を保持します。Web UI・API・MCPは同じ公開処理を使います。