BluePeriod Docs
開発

API仕様書

Honoフレームワーク基盤のAPIエンドポイント仕様

API仕様書 (05_api_specification.md)

本ドキュメントは、Hono フレームワークに集約された新 API 構造に基づいて記載されています。

1. 概要

本アプリケーションのバックエンドは、Next.jsのAPI Routes機能を利用して構築されています。これにより、フロントエンドと同じプロジェクト内でサーバーサイドロジックを管理し、特に外部のLLM(大規模言語モデル)プロバイダーとの連携を担います。

  • 設計思想:
    • API RoutesはBFF (Backend For Frontend) として機能し、クライアントからのリクエストを整形して各種LLMプロバイダーへ仲介します。
    • APIキーなどの機密情報は、原則としてサーバーサイド(環境変数)で管理しますが、ユーザーがクライアントサイドで入力したキーを優先的に使用する「Bring Your Own Key (BYOK)」モデルもサポートします。
エンドポイントHTTPメソッド主な役割分類 (サブアプリ)
/api/chatPOST汎用的なAIとの対話、エージェント実行chat.ts
/api/ai/reviewPOST作品原稿の査読レポート生成ai.ts
/api/ai/metadata-generatePOST作品のメタデータ案(タイトル等)生成ai.ts
/api/ai/metadata-translatePOSTメタデータの多言語翻訳ai.ts
/api/ai/structure-plotPOSTプロットの構造化ai.ts
/api/ai/refine-plot-chunkPOSTプロットアイテムの改修ai.ts
/api/publishPOST作品のクラウド公開publish.ts
/api/projects/statusPOSTプロジェクトの公開・非公開状態管理projects.ts
/api/libraryGET/POST本棚(ライブラリ)のアイテム取得・操作library.ts
/api/tools/web-searchPOSTWeb検索のプロキシtools.ts
/api/tools/open-urlPOSTURLの読込とMarkdown変換tools.ts
/api/tools/aditPOSTAIによる決定論的テキスト編集 (Adit)tools.ts
/api/models/openrouterGETOpenRouter のモデル一覧取得models.ts
/api/current-timeGETサーバー時刻の取得infra.ts
/api/seo/sitemapGETサイトマップの動的生成seo.ts

3. エンドポイント仕様詳細

3.1. GET /api/projects/[id]/metadata

指定されたprojectIdの公開済みメタデータ(title, synopsis, tagsなど)をSupabaseから取得します。

  • 目的: 公開済みプロジェクトを更新する際に、既存のメタデータをフォームの初期値として表示するために使用します。
  • 認証: 不要 (RLSにより公開データのみ返却されるため)
  • リクエストパラメータ (params):
    • id (必須): string。取得するプロジェクトのUUID。
  • 処理フロー:
    1. projectIdが存在しない場合は400 Bad Requestを返します。
    2. published_projectsテーブルから、指定されたidのプロジェクトのメタデータ(title, headline, synopsis, tags, title_original, headline_original, synopsis_original, original_language, cover_image_url)を取得します。
    3. プロジェクトが見つからない場合(PGRST116エラーコード)、null200 OKを返します。
  • 成功レスポンス (200 OK):
    {
      "title": "作品タイトル(英語)",
      "headline": "キャッチコピー(英語)",
      "synopsis": "あらすじ(英語)",
      "tags": ["tag1", "tag2"],
      "title_original": "作品タイトル(原典言語)",
      "headline_original": "キャッチコピー(原典言語)",
      "synopsis_original": "あらすじ(原典言語)",
      "original_language": "ja",
      "cover_image_url": "https://..."
    }
    • プロジェクトが見つからない場合は null
  • エラーレスポンス:
    • Status Code: 400 Bad Request, 500 Internal Server Error
    • Body:
      {
        "error": "エラーの概要",
        "details": "詳細なエラーメッセージ"
      }

3.2. PATCH /api/projects/[id]/metadata

指定されたprojectIdの公開済みメタデータを部分的に更新します。多言語対応(英語と原典言語)のメタデータ編集をサポートします。

  • 目的: マイワークス画面の「メタデータを編集」モーダルから、タイトル、あらすじ、カバー画像などを更新する際に使用します。
  • 認証: 必須。auth.users.idがプロジェクトの所有者(user_id)と一致する場合のみ更新が許可されます。
  • リクエストパラメータ (params):
    • id (必須): string。更新するプロジェクトのUUID。
  • リクエストボディ (JSON):
    {
      "title": "作品タイトル(英語)",
      "headline": "キャッチコピー(英語)", // (任意)
      "synopsis": "あらすじ(英語)", // (任意)
      "title_original": "作品タイトル(原典言語)", // (任意)
      "headline_original": "キャッチコピー(原典言語)", // (任意)
      "synopsis_original": "あらすじ(原典言語)", // (任意)
      "tags": ["tag1", "tag2"],
      "cover_image_url": "https://..." // (任意) nullの場合は削除
    }
  • 処理フロー:
    1. ユーザーセッションを検証し、未認証の場合は401 Unauthorizedを返します。
    2. リクエストボディを検証します(titleは必須、その他は任意)。
    3. 指定されたプロジェクトが存在し、かつリクエストユーザーが所有者であることを確認します。
    4. cover_image_urlが変更された場合、古いカバー画像をSupabase Storageから削除します。
    5. published_projectsテーブルのレコードを更新します。
  • 成功レスポンス (200 OK):
    {
      "message": "Metadata updated successfully"
    }
  • エラーレスポンス:
    • Status Code: 400 Bad Request (バリデーションエラー), 401 Unauthorized (未認証), 404 Not Found (プロジェクトなし/権限なし), 500 Internal Server Error
    • Body:
      {
        "error": "エラーメッセージ"
      }

3.3. POST /api/publish

査読済みの作品をSupabaseのデータベースとストレージに保存し、公開状態にします。

  • 目的: フロントエンドの公開ウィザードから送信された作品データを受け取り、永続化します。
  • 認証: 必須。リクエストヘッダーのCookieを元にSupabaseのサーバーサイドクライアントでセッションを検証します。
  • リクエストボディ (FormData):
    • file (任意): Fileオブジェクト。作品のカバー画像。
    • data (必須): string。以下の構造を持つJSONオブジェクトを文字列化したもの。
      {
        "id": "project-uuid-string",
        "title": "作品タイトル(英語)",
        "headline": "キャッチコピー(英語)",
        "synopsis": "あらすじ(英語)",
        "tags": ["tag1", "tag2"], // 英語と原典言語のタグがマージされた配列
        "title_original": "作品タイトル(原典言語)", // (任意)
        "headline_original": "キャッチコピー(原典言語)", // (任意)
        "synopsis_original": "あらすじ(原典言語)", // (任意)
        "original_language": "ja", // (任意) 原典言語コード
        "content": { ... }, // ProjectContent型の完全なオブジェクト
        "review_report": { ... } // (任意) AI査読レポート
      }
  • 処理フロー:
    1. ユーザーセッションを検証し、未認証の場合は401 Unauthorizedを返します。
    2. FormDataを解析します。dataフィールドが存在しない場合は400 Bad Requestを返します。
    3. fileが存在する場合、Supabase Storageのcoversバケットに{user_id}/{project_id}.{ext}のパスでアップロードし、公開URLを取得します。
    4. データ変換: content.manuscriptsを、structuredPlotの階層順序に基づいて配列形式 [{ sectionId, versions }, ...] に変換します(transformManuscriptsToArrayヘルパー関数を使用)。これにより、Reader Viewerでの表示順序が保証されます。
    5. published_projectsテーブルに、変換後のコンテンツと画像の公開URLをupsertします。
  • 成功レスポンス (200 OK):
    {
      "success": true,
      "projectId": "project-uuid-string"
    }
  • エラーレスポンス:
    • Status Code: 400 Bad Request, 401 Unauthorized, 500 Internal Server Error
    • Body:
      {
        "error": "エラーの概要",
        "details": "詳細なエラーメッセージ"
      }

3.2. POST /api/ai/review

作品原稿をAIが査読し、品質と安全性の評価レポートを生成します。レポートは常に英語で生成されます。

  • 目的: 作品の公開前に、AIによる自動査読を行い、公開可否の判断材料を提供します。
  • 認証: 必須。
  • リクエストボディ (JSON):
    {
      "manuscripts": { ... }, // ProjectContent.manuscripts と同形式
      "structuredPlot": { ... }, // StructuredPlot と同形式
      "apiKey": "gemini-api-key" // ユーザーのGemini APIキー
    }
    • manuscripts (必須): 査読対象の原稿データ。
    • structuredPlot (必須): 原稿の構造化されたプロットデータ。
    • apiKey (必須): Gemini APIキー。
  • 処理フロー:
    1. 提供された原稿とプロットから、査読のためのコンテキストを生成します。
    2. Gemini AIモデルを呼び出し、査読レポートを生成させます。
  • 成功レスポンス (200 OK):
    {
      "overall_assessment": {
        "is_publishable": true,
        "summary": "..."
      },
      "completion_status": {
        "is_complete": true,
        "reasoning": "..."
      },
      "prohibitions": [],
      "warnings": [],
      "dynamic_detailed_ratings": []
    }
    • ReviewReport スキーマに準拠したJSONオブジェクト。
  • エラーレスポンス:
    • Status Code: 400 Bad Request, 500 Internal Server Error
    • Body:
      {
        "error": "Failed to review content",
        "details": "詳細なエラーメッセージ"
      }

3.3. POST /api/ai/metadata-generate

作品のタイトル、あらすじ、タグなどのメタデータ案を、AIが原典言語で3つ生成します。

  • 目的: 作者の母語でのメタデータ作成をAIが支援し、ユーザーの負担を軽減します。
  • 認証: 必須。
  • リクエストボディ (JSON):
    {
      "manuscripts": { ... }, // ProjectContent.manuscripts と同形式
      "structuredPlot": { ... }, // StructuredPlot と同形式
      "apiKey": "gemini-api-key" // ユーザーのGemini APIキー
    }
    • manuscripts (必須): メタデータ生成の参考となる原稿データ。
    • structuredPlot (必須): メタデータ生成の参考となるプロットデータ。
    • apiKey (必須): Gemini APIキー。
  • 処理フロー:
    1. 提供された原稿とプロットから、メタデータ生成のためのコンテキストを生成します。
    2. Gemini AIモデルを呼び出し、原典言語でのメタデータ提案を3つ生成させます。
  • 成功レスポンス (200 OK):
    {
      "proposals": [
        {
          "title": "提案タイトル1",
          "headline": "提案キャッチコピー1",
          "synopsis": "提案あらすじ1",
          "tags": ["タグ1", "タグ2"]
        },
        // ... 他2つの提案
      ]
    }
    • MetadataGenerationResponse スキーマに準拠したJSONオブジェクト。
  • エラーレスポンス:
    • Status Code: 400 Bad Request, 500 Internal Server Error
    • Body:
      {
        "error": "Failed to generate metadata",
        "details": "詳細なエラーメッセージ"
      }

3.4. POST /api/ai/metadata-translate

確定した原典言語のメタデータを、AIが英語に翻訳します。

  • 目的: グローバル公開のために、原典言語のメタデータを高品質な英語に翻訳します。
  • 認証: 必須。
  • リクエストボディ (JSON):
    {
      "metadata": {
        "title": "原典タイトル",
        "headline": "原典キャッチコピー",
        "synopsis": "原典あらすじ",
        "tags": ["原典タグ1", "原典タグ2"]
      },
      "originalLanguage": "ja", // 原典言語コード (例: 'ja')
      "apiKey": "gemini-api-key" // ユーザーのGemini APIキー
    }
    • metadata (必須): 翻訳対象の原典言語メタデータ。
    • originalLanguage (必須): 原典言語のISO 639-1コード。
    • apiKey (必須): Gemini APIキー。
  • 処理フロー:
    1. Gemini AIモデルを呼び出し、提供されたメタデータを英語に翻訳させます。
  • 成功レスポンス (200 OK):
    {
      "title": "Translated Title",
      "headline": "Translated Headline",
      "synopsis": "Translated synopsis...",
      "tags": ["Translated Tag1", "Translated Tag2"]
    }
    • TranslatedMetadata スキーマに準拠したJSONオブジェクト。
  • エラーレスポンス:
    • Status Code: 400 Bad Request, 500 Internal Server Error
    • Body:
      {
        "error": "Failed to translate metadata",
        "details": "詳細なエラーメッセージ"
      }

3.5. POST /api/chat

汎用的なLLMとの対話を実現するエンドポイントです。ストリーミングと非ストリーミングの両方に対応しています。

  • 目的: ユーザーからのメッセージ履歴を受け取り、選択されたLLMプロバイダーとモデルに応じた設定でAPIを呼び出し、AIの応答を返却します。

  • リクエストボディ (JSON):

    {
      "messages": [
        { "role": "system", "content": "..." },
        { "role": "user", "content": "..." },
        { "role": "assistant", "content": "..." }
      ],
      "provider": "openai" | "google" | "anthropic" | "openrouter",
      "model": "gpt-4o",
      "userApiKeys": {
        "openai": "sk-...",
        "google": "..."
      },
      "temperature": 0.7,
      "topP": 0.95,
      "stream": true
    }
    • messages (必須): LangChain形式のメッセージ配列。
    • provider (必須): 使用するLLMプロバイダーID。
    • model (必須): 使用するモデル名。
    • userApiKeys (任意): ユーザーが提供したAPIキーのオブジェクト。
    • temperature (任意): 生成時の温度。デフォルトは0.7
    • topP (任意): トップPサンプリング。デフォルトは0.95
    • stream (任意): ストリーミング応答を要求するかどうか。デフォルトはtrue
  • 成功レスポンス:

    • ストリーミング (stream: true):
      • Content-Type: text/plain; charset=utf-f
      • Body: AIからの応答テキストがチャンク形式で逐次送信される。
    • 非ストリーミング (stream: false):
      • Content-Type: application/json
      • Body:
        {
          "content": "AIからの完全な応答テキスト"
        }
  • エラーレスポンス:

    • Status Code: 400 Bad Request, 500 Internal Server Error
    • Body:
      {
        "error": "エラーメッセージ",
        "details": "詳細情報"
      }

3.3. GET /api/models/openrouter

OpenRouter.aiで利用可能なLLMのモデルID一覧を取得します。

  • 目的: クライアントサイドのモデル選択UIに、動的に最新のモデルリストを提供します。

  • リクエスト: なし

  • 成功レスポンス:

    • Content-Type: application/json
    • Body:
      {
        "models": [
          "openai/gpt-4o",
          "google/gemini-2.5-pro",
          "anthropic/claude-3.5-sonnet",
          ...
        ]
      }
  • エラーレスポンス:

    • Status Code: 500 Internal Server Error
    • Body:
      {
        "error": "Failed to fetch models from OpenRouter.",
        "details": "詳細なエラーメッセージ"
      }

3.4. POST /api/refine-plot-chunk

指定されたプロットの単一アイテム(Part, Arc, Chapterなど)を、ユーザーの指示に基づきAIが改善(リファイン)し、更新されたJSONオブジェクトを返します。

  • 目的: ユーザーがプロットの特定の部分に集中し、AIと対話しながらその内容を洗練させることを可能にする。

  • リクエストボディ (JSON):

    {
      "targetItem": { ... },
      "itemType": "part" | "arc" | "chapter" | "story" | "section",
      "context": { ... },
      "instruction": "ユーザーからの具体的な指示テキスト...",
      "baseMessages": [
        { "role": "system", "content": "..." }
      ],
      "provider": "google",
      "model": "gemini-2.5-pro",
      "userApiKeys": { ... },
      "temperature": 0.7,
      "topP": 0.95
    }
    • targetItem (必須): リファイン対象となるプロットアイテムのJSONオブジェクト。
    • itemType (必須): targetItemの階層を示す文字列。
    • context (必須): 周辺の文脈を理解するための、プロジェクト全体のプロットオブジェクト。
    • instruction (必須): AIに対する具体的な改善指示。
    • baseMessages (必須): 執筆プロンプトなど、AIの振る舞いを規定するシステムメッセージ配列。
    • provider, model, userApiKeys, temperature, topP: /api/chatと同様。
  • 成功レスポンス:

    • Content-Type: application/json
    • Body: itemTypeで指定されたスキーマに準拠した、更新後のプロットアイテムのJSONオブジェクト。
      {
        "id": "...", // IDは変更されない
        "chapter_number": 1,
        "chapter_title": "(改善されたタイトル)",
        "theme": "(改善されたテーマ)",
        ...
      }
  • エラーレスポンス:

    • Status Code: 400 Bad Request, 500 Internal Server Error
    • Body:
      {
        "error": "Failed to refine plot chunk",
        "details": "詳細なエラーメッセージ"
      }

3.5. POST /api/structure-plot

自然言語で記述されたプロットを、Zodスキーマに基づいた厳密な階層構造のJSONオブジェクトに変換します。

  • 目的: ユーザーが自由形式で入力した小説のプロット案を、アプリケーションが解釈可能な形式的なデータ構造に変換し、執筆プロセスを支援します。

  • リクエストボディ (JSON):

    {
      "plotInput": "ユーザーが入力したプロットのテキスト...",
      "baseMessages": [
        { "role": "system", "content": "..." }
      ],
      "provider": "google",
      "model": "gemini-2.5-pro",
      "userApiKeys": { ... },
      "temperature": 0.5,
      "topP": 1.0
    }
    • plotInput (必須): 構造化対象のプロットテキスト。
    • baseMessages (必須): プロンプトカードなどから生成されたシステムプロンプトを含むメッセージ配列。
    • provider, model, userApiKeys, temperature, topP: /api/chatと同様。
  • 成功レスポンス:

    • Content-Type: application/json
    • Body: types.tsで定義されたStructuredPlot型に準拠したJSONオブジェクト。
      {
        "language": "ja",
        "parts": [
          {
            "part_number": 1,
            "part_title": "...",
            "arcs": [ ... ]
          }
        ]
      }
  • エラーレスポンス:

    • Status Code: 400 Bad Request, 500 Internal Server Error
    • Body:
      {
        "error": "Failed to structure plot",
        "details": "詳細なエラーメッセージ"
      }

4. 小説執筆生成の実装仕様(/api/chat の使用例)

小説のセクション執筆生成(generateSection)および翻訳(translateSection)は、専用のAPIエンドポイントではなく、汎用 /api/chat をプロンプトベースで使用しています。これにより、柔軟なプロンプト調整が可能ですが、仕様書への明記を怠ると実装の理解が難しくなります。

4.1. セクション生成 (generateSectionAtom の処理フロー)

  • トリガー: LeftPanel.tsx 内の生成ボタン(Playアイコン)から generateSectionAtom を呼び出し、addJob でジョブ化。
  • API呼び出し: POST /api/chat
  • 主なリクエストパラメータ(抜粋):
    {
      "messages": [
        // writingPromptsAtom から baseMessages を構築 (執筆プロンプトカード)
        { "role": "system", "content": "執筆ガイドライン..." },
        {
          "role": "user",
          "content": "# OVERALL PLOT STRUCTURE (全体プロットJSON)\n# 直近の文脈 (先行セクション)\n# 今回執筆する節 (章/話/節詳細, 目標文字数)\n# 言語生成モジュール規則 (読点控えめ, 繰り返し禁止)\n\n小説テキストのみ出力。"
        }
      ],
      "provider": "google|openai|anthropic|openrouter",
      "model": "gemini-2.5-pro|gpt-4o|...",
      "userApiKeys": { "google": "...", ... },
      "temperature": 0.7,
      "topP": 0.95,
      "stream": true
    }
  • 文脈構築: writingContextWindowAtom で制限した先行セクションのマヌスクリプト + 全体プロットJSON。
  • レスポンス処理: ストリーミングで manuscriptsAtom に更新、デバッグ情報記録 (debugInfo.prompt 等)。

4.2. セクション翻訳 (translateSectionAtom)

  • トリガー: Globeドロップダウンから translateSectionAtom
  • プロンプト: 「プロの文芸翻訳家。直前文脈 + 原典テキスト を自然な[targetLang]に翻訳。テキストのみ出力。」

詳細実装: next-app/src/stores/aiGenerationAtoms.ts

将来的に専用API(/api/ai/generate-section)化を検討。

4.3. POST /api/tools/web-search

AIエージェントによるWeb検索機能を提供するエンドポイントです。Tavily Search APIをプロキシし、最新のWeb情報を取得します。

  • 目的: エージェントモードのチャットで、AIが自律的にWeb検索を行えるようにします。

  • リクエストボディ (JSON):

    {
      "query": "検索クエリ文字列",
      "apiKey": "tvly-..." // ユーザーのTavily APIキー
    }
    • query (必須): 検索クエリ。
    • apiKey (必須): Tavily APIキー。
  • 成功レスポンス (200 OK):

    • Content-Type: application/json
    • Body:
      {
        "results": [
          {
            "title": "ページタイトル",
            "url": "https://example.com",
            "content": "ページ内容の要約...",
            "score": 0.95
          },
          ...
        ]
      }
  • エラーレスポンス:

    • Status Code: 400 Bad Request, 500 Internal Server Error
    • Body:
      {
        "error": "Failed to perform web search",
        "details": "詳細なエラーメッセージ"
      }

詳細実装: next-app/src/app/api/tools/web-search/route.ts


関連ドキュメント:

  • 12_ai_agent_architecture.md - エージェントアーキテクチャの詳細

On this page