Skip to content

API 概要

luno は用途に応じた 3 つの API を提供しています。フロントエンドからのコンテンツ取得・管理操作・AI エージェントとの連携、それぞれに適した API を使い分けてください。

API の種類

公開 API(Public API)

ベース URL: https://{your-domain}/public/v1

項目詳細
認証不要(API キー・トークンなし)
CORSAccess-Control-Allow-Origin: *(すべてのオリジンを許可)
用途公開コンテンツの取得・コンタクトフォーム送信・メディア配信
主なクライアントブラウザ・CDN・AI エージェント・外部システム
bash
# 認証なしで直接呼び出せる
curl https://your-domain.com/public/v1/form-sets/blog/entries

管理 API(Admin API)

ベース URL: https://{your-domain}/admin/v1

項目詳細
認証JWT Bearer トークン必須
用途コンテンツ作成・編集・承認・メンバー管理・設定変更
主なクライアントluno 管理画面(Admin SPA)
bash
# JWT トークンが必要
curl https://your-domain.com/admin/v1/form-sets \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."

エージェント API(Agent API)

ベース URL: https://{your-domain}/admin/v1(管理 API と同じ)

項目詳細
認証エージェント API キー(sk-agent- プレフィックス)
用途AI エージェント・自動化からのコンテンツ / スキーマ操作
キーの発行管理画面「設定」→「エージェント API キー」(/settings/api-keys
スコープfull(推奨・記事+スキーマ)/ content(記事のみ)/ schemafull の互換エイリアス)
bash
curl https://api.luno.rest/admin/v1/form-sets \
  -H "Authorization: Bearer sk-agent-xxxxxxxx"

認証の詳細

JWT 認証(管理 API)

管理画面へのログイン後、POST /admin/v1/auth/login/password でトークンを取得します。トークンは HS256 署名付き JWT で、有効期限は設定に依存します。

bash
# ログインしてトークンを取得
curl -X POST https://your-domain.com/admin/v1/auth/login/password \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]", "password": "your-password" }'
json
{
  "token": "eyJhbGciOiJIUzI1NiJ9...",
  "user": { "id": "uuid", "email": "[email protected]" }
}

以降のリクエストでは Authorization: Bearer <token> ヘッダーを付けます。

エージェント API キー認証

キーは管理画面から発行します:

  1. 「設定」→「エージェント API キー」→「新規作成」
  2. 名前とスコープ(通常は full。記事だけに絞るなら content)を選択
  3. 表示されたキー(sk-agent-…)を安全な場所に保存

普段は full を推奨します。権限を記事運用だけに絞るときは contentschemafull と同権限の互換エイリアスです。エージェントキーでは Form Set / Contact Form の削除はできません。

キーの取り扱い

API キーは発行時に一度だけ表示されます。GitHub リポジトリやフロントエンドのコードに直接埋め込まないでください。サーバーサイドの環境変数として管理してください。

レスポンス形式

すべてのレスポンスは Content-Type: application/json です。

成功レスポンス(単一リソース)

json
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "slug": "my-first-post",
  "data": {
    "title": "はじめての投稿",
    "body": "<p>本文...</p>"
  },
  "mediaUrls": {
    "cover": "https://your-domain.com/public/v1/media/asset-uuid"
  }
}

成功レスポンス(一覧)

json
{
  "items": [
    {
      "entry": { "id": "uuid", "slug": "my-post" },
      "published": { "revisionId": "uuid", "revision": 2, "updatedAt": "2025-01-15T10:00:00Z" }
    }
  ],
  "total": 42,
  "limit": 20,
  "offset": 0
}

エラーレスポンス

json
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Published entry not found"
  }
}

エラーコード一覧

コードHTTP ステータス説明対処
NOT_FOUND404リソースが見つからないslug や ID を確認
VALIDATION_ERROR400リクエストパラメータ不正エラーメッセージで詳細を確認
UNAUTHORIZED401認証が必要(トークン未設定・期限切れ)再ログインしてトークンを更新
FORBIDDEN403権限不足上位ロールのユーザーで操作
PLAN_REQUIRED403上位プランが必要な機能プランをアップグレード
CONFLICT409競合(slug 重複など)別の slug を使用
RATE_LIMITED429エージェント API のレート制限超過Retry-After 秒待ってから再試行
INTERNAL_ERROR500サーバー内部エラー時間を置いて再試行、解消しない場合はサポートへ

ページネーション

一覧エンドポイントはページネーションをサポートします。

パラメータデフォルト説明
pageinteger1ページ番号(1 始まり)
limitinteger201 ページあたりの件数(最大 100)
offsetintegerオフセット指定(page の代わりに使用可)

全件取得のサンプルコード

typescript
async function fetchAllEntries(formSetSlug: string) {
  const BASE = 'https://your-domain.com/public/v1'
  const all: unknown[] = []
  let page = 1
  const limit = 100

  while (true) {
    const res = await fetch(
      `${BASE}/form-sets/${formSetSlug}/entries?page=${page}&limit=${limit}&include_snapshot=true`
    )
    const { items, total, offset } = await res.json()
    all.push(...items)

    if (offset + limit >= total) break
    page++
  }

  return all
}

キャッシュと ETag

公開 API はすべてのレスポンスに ETag ヘッダーを付与します。クライアントは If-None-Match ヘッダーを送ることで、コンテンツが変更されていない場合に 304 Not Modified を受け取れます。

http
# 初回リクエスト
GET /public/v1/form-sets/blog/entries/my-post HTTP/1.1

← HTTP 200
← ETag: "550e8400e29b41d4a716446655440000"
← Cache-Control: public, max-age=60

# 2 回目(キャッシュ検証)
GET /public/v1/form-sets/blog/entries/my-post HTTP/1.1
If-None-Match: "550e8400e29b41d4a716446655440000"

← HTTP 304 Not Modified(変更なしの場合、ボディなし)

Cache-Control: public, max-age=60 も付与されるため、CDN(Cloudflare、Fastly など)によるキャッシュも自動的に有効です。

CORS

公開 API はすべてのオリジンからのアクセスを許可します:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type

ブラウザから直接 fetch() でアクセスできます。CORS 回避のためのプロキシ設定は不要です。

レート制限

API制限
公開 APICloudflare Workers の標準制限に準拠
管理 API(JWT)エージェントキーのレート制限対象外
エージェント APIキーごと: Free / Solo 60 / 60 秒、Standard+ 300 / 60 秒AI エージェント

キャッシュで制限を緩和

ETag / If-None-Match を活用することでリクエスト数を大幅に削減できます。コンテンツが変更されていない場合の 304 レスポンスはカウントが少なくなります。

次のステップ