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/chat | POST | 汎用的なAIとの対話、エージェント実行 | chat.ts |
/api/ai/review | POST | 作品原稿の査読レポート生成 | ai.ts |
/api/ai/metadata-generate | POST | 作品のメタデータ案(タイトル等)生成 | ai.ts |
/api/ai/metadata-translate | POST | メタデータの多言語翻訳 | ai.ts |
/api/ai/structure-plot | POST | プロットの構造化 | ai.ts |
/api/ai/refine-plot-chunk | POST | プロットアイテムの改修 | ai.ts |
/api/publish | POST | 作品のクラウド公開 | publish.ts |
/api/projects/status | POST | プロジェクトの公開・非公開状態管理 | projects.ts |
/api/library | GET/POST | 本棚(ライブラリ)のアイテム取得・操作 | library.ts |
/api/tools/web-search | POST | Web検索のプロキシ | tools.ts |
/api/tools/open-url | POST | URLの読込とMarkdown変換 | tools.ts |
/api/tools/adit | POST | AIによる決定論的テキスト編集 (Adit) | tools.ts |
/api/models/openrouter | GET | OpenRouter のモデル一覧取得 | models.ts |
/api/current-time | GET | サーバー時刻の取得 | infra.ts |
/api/seo/sitemap | GET | サイトマップの動的生成 | seo.ts |
3. エンドポイント仕様詳細
3.1. GET /api/projects/[id]/metadata
指定されたprojectIdの公開済みメタデータ(title, synopsis, tagsなど)をSupabaseから取得します。
- 目的: 公開済みプロジェクトを更新する際に、既存のメタデータをフォームの初期値として表示するために使用します。
- 認証: 不要 (RLSにより公開データのみ返却されるため)
- リクエストパラメータ (
params):id(必須):string。取得するプロジェクトのUUID。
- 処理フロー:
projectIdが存在しない場合は400 Bad Requestを返します。published_projectsテーブルから、指定されたidのプロジェクトのメタデータ(title,headline,synopsis,tags,title_original,headline_original,synopsis_original,original_language,cover_image_url)を取得します。- プロジェクトが見つからない場合(
PGRST116エラーコード)、nullと200 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": "詳細なエラーメッセージ" }
- Status Code:
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の場合は削除 } - 処理フロー:
- ユーザーセッションを検証し、未認証の場合は
401 Unauthorizedを返します。 - リクエストボディを検証します(
titleは必須、その他は任意)。 - 指定されたプロジェクトが存在し、かつリクエストユーザーが所有者であることを確認します。
cover_image_urlが変更された場合、古いカバー画像をSupabase Storageから削除します。published_projectsテーブルのレコードを更新します。
- ユーザーセッションを検証し、未認証の場合は
- 成功レスポンス (200 OK):
{ "message": "Metadata updated successfully" } - エラーレスポンス:
- Status Code:
400 Bad Request(バリデーションエラー),401 Unauthorized(未認証),404 Not Found(プロジェクトなし/権限なし),500 Internal Server Error - Body:
{ "error": "エラーメッセージ" }
- Status Code:
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査読レポート }
- 処理フロー:
- ユーザーセッションを検証し、未認証の場合は
401 Unauthorizedを返します。 FormDataを解析します。dataフィールドが存在しない場合は400 Bad Requestを返します。fileが存在する場合、Supabase Storageのcoversバケットに{user_id}/{project_id}.{ext}のパスでアップロードし、公開URLを取得します。- データ変換:
content.manuscriptsを、structuredPlotの階層順序に基づいて配列形式[{ sectionId, versions }, ...]に変換します(transformManuscriptsToArrayヘルパー関数を使用)。これにより、Reader Viewerでの表示順序が保証されます。 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": "詳細なエラーメッセージ" }
- Status Code:
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キー。
- 処理フロー:
- 提供された原稿とプロットから、査読のためのコンテキストを生成します。
- 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": "詳細なエラーメッセージ" }
- Status Code:
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キー。
- 処理フロー:
- 提供された原稿とプロットから、メタデータ生成のためのコンテキストを生成します。
- 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": "詳細なエラーメッセージ" }
- Status Code:
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キー。
- 処理フロー:
- 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": "詳細なエラーメッセージ" }
- Status Code:
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からの応答テキストがチャンク形式で逐次送信される。
- Content-Type:
- 非ストリーミング (
stream: false):- Content-Type:
application/json - Body:
{ "content": "AIからの完全な応答テキスト" }
- Content-Type:
- ストリーミング (
-
エラーレスポンス:
- Status Code:
400 Bad Request,500 Internal Server Error - Body:
{ "error": "エラーメッセージ", "details": "詳細情報" }
- Status Code:
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", ... ] }
- Content-Type:
-
エラーレスポンス:
- Status Code:
500 Internal Server Error - Body:
{ "error": "Failed to fetch models from OpenRouter.", "details": "詳細なエラーメッセージ" }
- Status Code:
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": "(改善されたテーマ)", ... }
- Content-Type:
-
エラーレスポンス:
- Status Code:
400 Bad Request,500 Internal Server Error - Body:
{ "error": "Failed to refine plot chunk", "details": "詳細なエラーメッセージ" }
- Status Code:
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": [ ... ] } ] }
- Content-Type:
-
エラーレスポンス:
- Status Code:
400 Bad Request,500 Internal Server Error - Body:
{ "error": "Failed to structure plot", "details": "詳細なエラーメッセージ" }
- Status Code:
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 }, ... ] }
- Content-Type:
-
エラーレスポンス:
- Status Code:
400 Bad Request,500 Internal Server Error - Body:
{ "error": "Failed to perform web search", "details": "詳細なエラーメッセージ" }
- Status Code:
詳細実装: next-app/src/app/api/tools/web-search/route.ts
関連ドキュメント:
12_ai_agent_architecture.md- エージェントアーキテクチャの詳細