Skip to main content

API Documentation

REST API documentation based on OpenAPI 3.1.0 specification

API Overview

The PowerPoint Translation Tool API provides features for uploading PPTX files, extracting text, translating, and downloading.

Authentication

The API supports Bearer authentication using Supabase JWT tokens. Include Authorization: Bearer <token> in your request headers.

Base URL

Current Base URL: https://trans-ppt.work

Key Endpoints

File Upload (Server Action)

PPTX file uploads are performed via Server Actions. Files up to 25MB are supported. Direct API Route calls are not available.

extractTextFromPPTXAction (Server Action)

Extracts text elements from uploaded PPTX files via Server Action.

// Server Action (extractTextFromPPTXAction)
// クライアントコンポーネントから直接呼び出し可能
import { extractTextFromPPTXAction } from "@/app/actions/pptx";
const result = await extractTextFromPPTXAction(fileId, filePath);

POST /api/translate

Translates specified text to the target language. Batch translation is also supported.

curl -X POST https://trans-ppt.work/api/translate/stream \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "texts": [{"id": "1", "text": "Hello"}],
    "targetLanguage": "ja"
  }'

DELETE /api/files/{id}

Deletes the file with the specified ID.

curl -X DELETE https://trans-ppt.work/api/files/{id} \
  -H "Authorization: Bearer <token>"

GET /api/health

Checks system health. No authentication required.

curl https://trans-ppt.work/api/health

OpenAPI Specification

The complete OpenAPI specification can be downloaded or viewed from the links below.

openapi: 3.1.0
info:
  title: PPT Translation API
  version: 1.0.0
  description: |
    PowerPoint プレゼンテーション翻訳サービスのAPI仕様書
    
    このAPIは、PPTXファイルのアップロード、テキスト抽出、翻訳、ダウンロードなどの機能を提供します。
  contact:
    name: API Support
  license:
    name: MIT
    identifier: MIT

servers:
  - url: "{baseUrl}"
    description: |
      本番環境: NEXT_PUBLIC_APP_URL で決定される。
      デプロイ前に実際のURLへ差し替えること。
    variables:
      baseUrl:
        default: http://localhost:3000
        description: アプリケーションのベースURL(環境変数 NEXT_PUBLIC_APP_URL)
  - url: http://localhost:3000
    description: 開発環境

tags:
  - name: Files
    description: ファイル操作関連のエンドポイント
  - name: Translation
    description: 翻訳関連のエンドポイント
  - name: Preview
    description: ハイブリッドプレビュー関連のエンドポイント(PDF/サムネイル生成)
  - name: Auth
    description: 認証関連のエンドポイント
  - name: Stripe
    description: Stripe決済・サブスクリプション関連のエンドポイント
  - name: Health
    description: ヘルスチェック関連のエンドポイント
  - name: Admin
    description: 管理者専用エンドポイント(withAdminGetHandler / withAdminPostHandler 経由)

paths:
  # NOTE: ファイルアップロード・一覧取得はServer Action(src/app/actions/)で実装。
  # GET /api/files(一覧)は Issue #403 完了に伴い削除済み。

  /api/files/{id}:
    delete:
      summary: ファイルを削除
      description: 指定されたIDのファイルを削除します。
      operationId: deleteFile
      x-audience: public
      x-auth: cookie
      tags:
        - Files
      security:
        - CookieAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: ファイルID
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: 削除成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/translate/jobs:
    post:
      summary: durable翻訳ジョブを作成
      operationId: createTranslationJob
      x-audience: public
      x-auth: cookie
      tags: [Translation]
      security: [{ CookieAuth: [] }]
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema: { type: string, minLength: 1, maxLength: 255 }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/TranslationJobCreateRequest' }
      responses:
        '202':
          description: queued job
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TranslationJobResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/Conflict' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
        '500': { $ref: '#/components/responses/InternalServerError' }

  /api/translate/jobs/{jobId}:
    get:
      summary: durable翻訳ジョブ状態を取得
      operationId: getTranslationJob
      x-audience: public
      x-auth: cookie
      tags: [Translation]
      security: [{ CookieAuth: [] }]
      parameters:
        - $ref: '#/components/parameters/TranslationJobId'
      responses:
        '200':
          description: owner-scoped job status
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TranslationJobResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
        '500': { $ref: '#/components/responses/InternalServerError' }

  /api/translate/jobs/{jobId}/cancel:
    post:
      summary: durable翻訳ジョブをキャンセル
      operationId: cancelTranslationJob
      x-audience: public
      x-auth: cookie
      tags: [Translation]
      security: [{ CookieAuth: [] }]
      parameters: [{ $ref: '#/components/parameters/TranslationJobId' }]
      responses:
        '200':
          description: cancelled job
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TranslationJobResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/translate/jobs/{jobId}/retry:
    post:
      summary: durable翻訳ジョブを同じjobで再試行
      operationId: retryTranslationJob
      x-audience: public
      x-auth: cookie
      tags: [Translation]
      security: [{ CookieAuth: [] }]
      parameters: [{ $ref: '#/components/parameters/TranslationJobId' }]
      responses:
        '200':
          description: requeued job
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TranslationJobResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/translate/jobs/{jobId}/terminal-notice/claim:
    post:
      summary: terminal toast表示権を原子的にclaim
      operationId: claimTranslationJobTerminalNotice
      x-audience: public
      x-auth: cookie
      tags: [Translation]
      security: [{ CookieAuth: [] }]
      parameters: [{ $ref: '#/components/parameters/TranslationJobId' }]
      responses:
        '200':
          description: claim result
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TranslationJobTerminalNoticeClaim' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/translate/single:
    post:
      summary: 単一テキストを翻訳
      description: |
        単一テキストを目標言語に翻訳します。
        優先度キュー対応、DBキャッシュ統合、AbortSignal対応。
      operationId: translateSingle
      x-audience: public
      x-auth: cookie
      tags:
        - Translation
      security:
        - CookieAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: 同一リクエストの再送を識別する冪等性キー
          schema:
            type: string
            minLength: 1
            maxLength: 255
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SingleTranslationRequest'
      responses:
        '200':
          description: 翻訳成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SingleTranslationResponse'
        '400':
          description: 入力エラー
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SingleTranslationResponse'
        '401':
          description: 認証エラー
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SingleTranslationResponse'
        '403':
          description: 翻訳機能または権限が利用できない
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SingleTranslationResponse'
        '404':
          description: 対象ファイルが見つからない
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SingleTranslationResponse'
        '409':
          description: 同一の冪等性キーで翻訳処理中
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SingleTranslationResponse'
        '422':
          description: 冪等性キーのリクエスト不一致
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SingleTranslationResponse'
        '429':
          description: レート制限を超過
          headers:
            Retry-After:
              description: 再試行可能になるまでの秒数
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SingleTranslationResponse'
        '499':
          description: クライアントによる翻訳キャンセル(サーバー側観測用。クライアントabort後は通常このレスポンスを受信しない)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SingleTranslationResponse'
        '500':
          description: 翻訳エラー
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SingleTranslationResponse'
        '503':
          description: 翻訳機能停止中
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SingleTranslationResponse'

  /api/translate/stream:
    post:
      summary: ストリーミング翻訳(SSE)
      description: |
        Server-Sent Events(SSE)を使用してリアルタイムに翻訳結果をストリーミングします。
        同時接続数はユーザーあたり最大3本に制限されます。
      operationId: translateStream
      x-audience: public
      x-auth: cookie
      tags:
        - Translation
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TranslationRequest'
      responses:
        '200':
          description: SSEストリーミング翻訳レスポンス
          content:
            text/event-stream:
              schema:
                type: string
                description: |
                  Server-Sent Eventsストリーム。
                  各イベントはJSON形式で翻訳の進捗・結果を含む。
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/health:
    get:
      summary: ヘルスチェック
      description: システムの健全性を確認します。
      operationId: healthCheck
      x-audience: internal
      x-auth: none
      tags:
        - Health
      security: []
      responses:
        '200':
          description: システムは正常に動作しています
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: サービスが利用できません
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/stripe/webhook:
    post:
      summary: Stripe Webhook受信
      description: |
        Stripeからのイベント通知を受信し、処理します。
        署名検証を必須とし、べき等性を保証します。
      operationId: handleStripeWebhook
      x-audience: internal
      x-auth: signature
      tags:
        - Stripe
      security: []  # Webhookは署名ヘッダーで検証するためBearerトークン不要
      parameters:
        - in: header
          name: stripe-signature
          required: true
          schema:
            type: string
          description: Stripe署名ヘッダー
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Stripeイベントペイロード
      responses:
        '200':
          description: Webhook処理成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  received:
                    type: boolean
                    example: true
        '400':
          description: 無効な署名または不正なペイロード
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalServerError'

  # NOTE: Customer Portal Session作成はServer Action(src/app/actions/payment.ts)で実装。
  # API Routeとしては存在しない。

  # ============================================================================
  # Preview API エンドポイント(ハイブリッドプレビュー機能)
  # ============================================================================

  /api/preview/start:
    post:
      summary: プレビュージョブ開始
      description: |
        PPTXファイルのプレビュージョブを開始します。
        Python service が所有者確認済みの Storage bytes と実際の converter identity を使ってジョブを解決し、
        新規作成時は同じ Python request 内で PDF を生成します。

        同一 file、PPTX bytes の SHA-256、converter identity の active job は再利用されます。
        同一 identity の completed と completed_with_errors はどちらも再利用されます。
        completed_with_errors の再利用は既存の失敗ページと retry count を維持します。
        異なる identity の active job がある場合は 409 です。
        Python が実際の converter identity を確認できない場合は、job を作成せず 503 です。
      operationId: startPreviewJob
      x-audience: public
      x-auth: cookie
      tags:
        - Preview
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PreviewStartRequest'
      responses:
        '200':
          description: ジョブ開始成功(または既存ジョブを返却)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreviewStartResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: 別 identity のプレビュージョブが既に実行中
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          description: Python converter identity または依存サービスを利用できない
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/preview/status:
    get:
      summary: プレビュージョブステータス取得
      description: |
        プレビュージョブの状態とページ一覧を返します。
        ポーリングで進捗確認する用途で使用します。
      operationId: getPreviewStatus
      x-audience: public
      x-auth: cookie
      tags:
        - Preview
      security:
        - CookieAuth: []
      parameters:
        - name: jobId
          in: query
          required: true
          description: プレビュージョブID
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: ステータス取得成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreviewStatusResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/preview/pdf-url:
    get:
      summary: PDF署名URL取得
      description: |
        PDFの一時的な閲覧URLを生成して返します。
        URLは1時間有効です。
      operationId: getPreviewPdfUrl
      x-audience: public
      x-auth: cookie
      tags:
        - Preview
      security:
        - CookieAuth: []
      parameters:
        - name: jobId
          in: query
          required: true
          description: プレビュージョブID
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: URL取得成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreviewSignedUrlResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: ジョブが見つからない、またはPDFが未生成
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/preview/thumb-url:
    get:
      summary: サムネイル署名URL取得
      description: |
        指定ページのサムネイル一時閲覧URLを生成して返します。
        URLは1時間有効です。
      operationId: getPreviewThumbUrl
      x-audience: public
      x-auth: cookie
      tags:
        - Preview
      security:
        - CookieAuth: []
      parameters:
        - name: jobId
          in: query
          required: true
          description: プレビュージョブID
          schema:
            type: string
            format: uuid
        - name: page
          in: query
          required: true
          description: ページインデックス(0始まり)
          schema:
            type: integer
            minimum: 0
      responses:
        '200':
          description: URL取得成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreviewSignedUrlResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: ジョブが見つからない、またはサムネイルが未生成
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/preview/thumb-urls:
    get:
      summary: サムネイル署名URL一括取得
      description: |
        指定ジョブの done ページのサムネイル署名URLをバッチで返す。
        個別 /api/preview/thumb-url の N+1 問題を解消する。
        URL は1時間有効。
      operationId: getPreviewThumbUrls
      x-audience: public
      x-auth: cookie
      tags:
        - Preview
      security:
        - CookieAuth: []
      parameters:
        - name: jobId
          in: query
          required: true
          description: プレビュージョブID
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: 一括URL取得成功(page_index → signed URL のマップ)
          content:
            application/json:
              schema:
                type: object
                required:
                  - urls
                properties:
                  urls:
                    type: object
                    additionalProperties:
                      type:
                        - string
                        - 'null'
                      description: 署名済み URL(thumb 未生成の場合は null)
                    description: page_index をキー、署名URLを値とするマップ
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/preview/thumbs-start:
    post:
      summary: サムネイル生成開始
      description: |
        Cloud Runにサムネイル生成を依頼します。
        リースロックにより多重起動は防止されます。
      operationId: startPreviewThumbs
      x-audience: public
      x-auth: cookie
      tags:
        - Preview
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PreviewThumbsStartRequest'
      responses:
        '200':
          description: サムネイル生成開始成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreviewThumbsStartResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: サービス未設定(PYTHON_SERVICE_URLが未設定)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/preview/thumbs-retry:
    post:
      summary: 失敗ページ一括再生成
      description: |
        failed状態の全ページを再生成します。
      operationId: retryPreviewThumbs
      x-audience: public
      x-auth: cookie
      tags:
        - Preview
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PreviewThumbsRetryRequest'
      responses:
        '200':
          description: 再生成開始成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreviewThumbsRetryResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: サービス未設定
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/preview/thumbs-retry-page:
    post:
      summary: 単一ページ再生成
      description: |
        指定ページのサムネイルを再生成します。
      operationId: retryPreviewThumbPage
      x-audience: public
      x-auth: cookie
      tags:
        - Preview
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PreviewThumbsRetryPageRequest'
      responses:
        '200':
          description: 再生成成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreviewThumbsRetryPageResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: サービス未設定
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  # ============================================================================
  # Auth エンドポイント
  # ============================================================================

  /api/auth/callback:
    get:
      summary: OAuth/Magic Linkコールバック
      description: |
        Supabase Auth の PKCE フロー完了後にリダイレクトされるコールバック。
        認証コードをセッションに交換し、next パラメータへリダイレクトする。
      operationId: authCallback
      x-audience: public
      x-auth: none
      tags:
        - Auth
      security: []
      parameters:
        - name: code
          in: query
          description: Supabase Auth が発行する認証コード
          schema:
            type: string
        - name: next
          in: query
          description: 認証後のリダイレクト先パス(sanitizeNextPath で検証)
          schema:
            type: string
            default: /dashboard
      responses:
        '307':
          description: |
            認証成功時は next へリダイレクト。
            失敗時(コード無効・未提供)は /login?error=auth_callback_error へリダイレクト。
            Rate Limit超過時は /login?error=rate_limited へリダイレクト。

  /api/auth/csrf:
    get:
      summary: CSRFトークン取得
      description: |
        フォーム送信前にCSRFトークンを取得するエンドポイント。
        Rate Limit 適用。キャッシュ無効化ヘッダー付き。
      operationId: getCsrfToken
      x-audience: public
      x-auth: none
      tags:
        - Auth
      security: []
      responses:
        '200':
          description: トークン取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    required:
                      - data
                    properties:
                      data:
                        type: object
                        required:
                          - csrfToken
                        properties:
                          csrfToken:
                            type: string
                            description: CSRFトークン
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/auth/session:
    get:
      summary: セッション確認
      description: |
        現在のユーザーセッションを検証し、ユーザー情報を返す。
        Origin 検証 + Rate Limit 適用。
      operationId: getSession
      x-audience: public
      x-auth: cookie
      tags:
        - Auth
      security:
        - CookieAuth: []
      responses:
        '200':
          description: セッション有効
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                  - data
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    required:
                      - user
                      - session
                    properties:
                      user:
                        type: object
                        required:
                          - id
                        additionalProperties: false
                        properties:
                          id:
                            type: string
                            format: uuid
                          email:
                            type: string
                            format: email
                      session:
                        type: object
                        required:
                          - user
                        properties:
                          user:
                            type: object
                            required:
                              - id
                            additionalProperties: false
                            properties:
                              id:
                                type: string
                                format: uuid
                              email:
                                type: string
                                format: email
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /api/auth/logout:
    post:
      summary: ログアウト
      description: |
        Supabase Auth からサインアウトし、Clear-Site-Data ヘッダーで
        Cookie/Storage を一括クリアする。Origin 検証 + Rate Limit 適用。
      operationId: logout
      x-audience: public
      x-auth: cookie
      tags:
        - Auth
      security:
        - CookieAuth: []
      responses:
        '200':
          description: ログアウト成功
          headers:
            Clear-Site-Data:
              description: Cookie/Storage クリア指示
              schema:
                type: string
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                  - data
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                      redirectTo:
                        type: string
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /api/auth/is-admin:
    get:
      summary: 管理者判定
      description: |
        現在のユーザーが管理者かどうかを profiles テーブルの role で判定する。
        未認証の場合は isAdmin: false を返す(エラーではない)。
      operationId: isAdmin
      x-audience: public
      x-auth: cookie
      tags:
        - Auth
      security:
        - CookieAuth: []
      responses:
        '200':
          description: 判定結果
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                  - data
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    required:
                      - isAdmin
                    properties:
                      isAdmin:
                        type: boolean
                      reason:
                        type: string
                        enum: [profiles, none, unauthenticated, error]
                      hasProfile:
                        type: boolean
                      email:
                        type: string
                        description: マスク済みメールアドレス
        '429':
          $ref: '#/components/responses/TooManyRequests'

  # ============================================================================
  # Files 追加エンドポイント
  # ============================================================================

  /api/files/{id}/download:
    get:
      summary: ファイルダウンロード
      description: |
        元ファイルまたは翻訳済みファイルをダウンロードする。
        ファイル名を指定した短期署名URLを生成し、Storageへ一時リダイレクトする。
        アプリケーションサーバーはファイル本体をバッファリングしない。
        FILE_DOWNLOAD監査イベントはStorage取得完了ではなく署名URL発行時に記録する。
        Rate Limit 適用(ダウンロード専用プリセット)。
      operationId: downloadFile
      x-audience: public
      x-auth: cookie
      tags:
        - Files
      security:
        - CookieAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: ファイルID
          schema:
            type: string
            format: uuid
        - name: type
          in: query
          description: ダウンロード種別
          schema:
            type: string
            enum: [original, translated]
            default: original
      responses:
        '302':
          description: Supabase Storageの短期署名URLへリダイレクト
          headers:
            Location:
              required: true
              description: downloadファイル名を含む短期署名URL
              schema:
                type: string
                format: uri
            X-Request-Id:
              description: リクエスト追跡ID
              schema:
                type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  # ============================================================================
  # Usage エンドポイント
  # ============================================================================

  /api/usage/check:
    get:
      summary: 使用量チェック
      description: |
        ユーザーの月間使用量と残り翻訳可能ファイル数を返す。
        Origin 検証適用。
      operationId: checkUsage
      x-audience: public
      x-auth: cookie
      tags:
        - Files
      security:
        - CookieAuth: []
      responses:
        '200':
          description: 使用量情報
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UsageCheckResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'

  # ============================================================================
  # Error Report エンドポイント
  # ============================================================================

  /api/error-report:
    post:
      summary: クライアントエラーレポート
      description: |
        クライアント側で発生したエラーをサーバーに報告する。
        error-boundary-wrapper.tsx から自動送信される。
        CSRF + Origin 検証 + Rate Limit 適用。匿名ユーザーも受理可能。
      operationId: reportError
      x-audience: public
      x-auth: none
      tags:
        - Health
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ErrorReportRequest'
      responses:
        '200':
          description: レポート受信成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  requestId:
                    type: string
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  # ============================================================================
  # Internal Health エンドポイント
  # ============================================================================

  # ============================================================================
  # Admin API エンドポイント(withAdmin*Handler 経由・admin role 必須)
  # ============================================================================
  # NOTE: /api/admin/* は順次 OpenAPI に追記中(#1100 / #1105 follow-up)。
  # 本セクションは Phase A: GET 系の登録から開始。

  /api/admin/overview:
    get:
      summary: 管理者ダッシュボード概要
      description: |
        管理者向けダッシュボードに表示する集計データを返す。
        ユーザー集計・ファイル集計・直近のアクティビティログ・当月の利用統計を一括取得する。
        Service role client を使用するため、`withAdminGetHandler` で admin role を必須とする。
      operationId: getAdminOverview
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      responses:
        '200':
          description: 集計データ取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/AdminOverviewResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/admin/analytics:
    get:
      summary: 翻訳・利用分析データ
      description: |
        翻訳パイプラインの分析データを返す。
        クエリパラメータで取得対象を切り替える: `summary` / `failure` / `performance` / `language` / `retry` / `timeseries` / `translation`。
        SSOT: `src/lib/analytics/queries.ts` および `src/lib/admin/translation-analytics.ts`。
        レスポンス shape は type ごとに異なるため、本仕様では汎用 object として表現する。
      operationId: getAdminAnalytics
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      parameters:
        - in: query
          name: type
          schema:
            type: string
            enum: [summary, failure, performance, language, retry, timeseries, translation]
            default: summary
        - in: query
          name: startDate
          schema:
            type: string
            format: date-time
          description: ISO 8601 形式(snake_case の `start_date` も受理)
        - in: query
          name: endDate
          schema:
            type: string
            format: date-time
          description: ISO 8601 形式(snake_case の `end_date` も受理)
        - in: query
          name: granularity
          schema:
            type: string
            enum: [hour, day, week]
          description: timeseries 取得時のみ有効
      responses:
        '200':
          description: 分析データ取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
                        description: type パラメータごとに異なる集計結果
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/admin/health:
    get:
      summary: 管理者用ヘルスメトリクス
      description: |
        エラー率・応答時間・API 成功率などのシステムヘルスメトリクスを返す。
        SSOT: `src/lib/admin/health-monitoring.ts`。
        `/api/health` (public uptime monitor) や `/api/internal/health` (deep check) とは別系統。
      operationId: getAdminHealthMetrics
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      parameters:
        - in: query
          name: start_date
          schema:
            type: string
            format: date-time
        - in: query
          name: end_date
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: ヘルスメトリクス取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/admin/operations:
    get:
      summary: オペレーションメトリクス
      description: |
        失敗率・p50/p95 レイテンシ・リトライ分布などのオペレーションメトリクスを返す。
        SSOT: `src/lib/analytics/operations-metrics.ts`。
      operationId: getAdminOperationsMetrics
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      parameters:
        - in: query
          name: days
          schema:
            type: integer
            default: 30
            minimum: 1
          description: 集計対象の過去日数
      responses:
        '200':
          description: オペレーションメトリクス取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/admin/quality:
    get:
      summary: 翻訳品質メトリクス
      description: |
        ダウンロード率・再翻訳率・編集率などの品質指標を返す。
        SSOT: `src/lib/analytics/quality-metrics.ts`。
      operationId: getAdminQualityMetrics
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      parameters:
        - in: query
          name: days
          schema:
            type: integer
            minimum: 1
            maximum: 365
            default: 30
          description: 集計対象とする過去日数
      responses:
        '200':
          description: 品質メトリクス取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/QualityMetrics'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/admin/revenue:
    get:
      summary: 収益メトリクス
      description: |
        MRR / ARR / 解約率 / 支払い失敗 / 返金などの収益指標を返す。
        Stripe データと内部 subscription state を集計。
      operationId: getAdminRevenueMetrics
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      responses:
        '200':
          description: 収益メトリクス取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/admin/costs:
    get:
      summary: コスト監視メトリクス
      description: |
        Claude API 使用量・Supabase Storage 使用量を集計する。
        SSOT: `src/lib/admin/cost-monitoring.ts`。
      operationId: getAdminCostMetrics
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      responses:
        '200':
          description: コストメトリクス取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/admin/abuse:
    get:
      summary: 不正利用検知
      description: |
        異常利用パターン(短時間大量アップロード等)を検出する。
        SSOT: `src/lib/admin/abuse-detection.ts`。
      operationId: getAdminAbuseDetection
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      responses:
        '200':
          description: 検知結果取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/admin/security:
    get:
      summary: セキュリティメトリクス
      description: |
        Rate Limit 統計・認証エラー・異常検知・スタックファイル検知などを集計する。
        SSOT: `src/lib/analytics/anomaly-detection.ts`, `src/lib/analytics/stuck-detection.ts`。
      operationId: getAdminSecurityMetrics
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      responses:
        '200':
          description: セキュリティメトリクス取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/admin/users:
    get:
      summary: ユーザー一覧(管理者用)
      description: |
        管理者向けユーザー一覧を返す。フィルタ・ソート・ページネーション可。
        SSOT: `src/lib/admin/users.ts`。
      operationId: getAdminUsers
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      parameters:
        - in: query
          name: search
          schema:
            type: string
          description: ユーザー名 / email 部分一致
        - in: query
          name: role
          schema:
            type: string
            enum: [user, admin]
        - in: query
          name: is_blocked
          schema:
            type: string
            enum: ["true", "false"]
          description: ブロック状態フィルタ(boolean を文字列で渡す)
        - in: query
          name: has_subscription
          schema:
            type: string
            enum: ["true", "false"]
        - in: query
          name: plan_name
          schema:
            type: string
            enum: [free, standard, pro]
        - in: query
          name: sort_by
          schema:
            type: string
            default: created_at
        - in: query
          name: sort_order
          schema:
            type: string
            enum: [asc, desc]
            default: desc
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - in: query
          name: offset
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: ユーザー一覧取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/admin/users/{id}:
    get:
      summary: ユーザー詳細(管理者用)
      description: 指定 ID のユーザー詳細・関連 metadata を返す。
      operationId: getAdminUserDetail
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: ユーザー詳細取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/admin/users/{id}/block:
    post:
      summary: ユーザーブロック切替
      description: |
        指定 ID のユーザーのブロック状態を変更する。
        body の `is_blocked` で true/false を切替。
      operationId: postAdminUserBlock
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - is_blocked
              properties:
                is_blocked:
                  type: boolean
      responses:
        '200':
          description: ブロック状態変更成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/admin/users/{id}/subscription:
    post:
      summary: ユーザーサブスクリプション変更
      description: |
        指定 ID のユーザーのプランを変更する。Stripe との同期は内部で実施。
      operationId: postAdminUserSubscription
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - plan_name
              properties:
                plan_name:
                  type: string
                  enum: [free, standard, pro]
      responses:
        '200':
          description: プラン変更成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/admin/recover-stuck:
    get:
      summary: スタックファイル一覧
      description: |
        翻訳が長時間進まないファイルを一覧で返す(運用復旧支援)。
        SSOT: `src/lib/analytics/stuck-detection.ts`。
      operationId: getAdminRecoverStuck
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      responses:
        '200':
          description: スタックファイル一覧取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      summary: スタックファイル復旧実行
      description: |
        thresholdMs を超えて停止しているファイルの翻訳を再キックする。
        body 省略時は default の閾値で実行する。
      operationId: postAdminRecoverStuck
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                thresholdMs:
                  type: integer
                  minimum: 1
                  description: スタック判定の閾値(ミリ秒)。省略時は default を使用
      responses:
        '200':
          description: 復旧処理実行成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/admin/metrics:
    get:
      summary: 監視メトリクス
      description: |
        直近1時間の翻訳実行イベント成功/失敗率とファイル数を返す。
        翻訳実行イベントが0件の場合、失敗率は未計測として null を返す。
        5 分間のキャッシュ付き。
      operationId: getAdminMonitoringMetrics
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      responses:
        '200':
          description: メトリクス取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        required: [metrics, cached]
                        properties:
                          metrics:
                            type: object
                            required:
                              - cachedAt
                              - failedTranslationExecutionsLastHour
                              - totalFilesLastHour
                              - totalTranslationExecutionsLastHour
                              - translationFailureRate
                            properties:
                              cachedAt:
                                type: string
                                format: date-time
                              failedTranslationExecutionsLastHour:
                                type: integer
                                minimum: 0
                              totalFilesLastHour:
                                type: integer
                                minimum: 0
                              totalTranslationExecutionsLastHour:
                                type: integer
                                minimum: 0
                              translationFailureRate:
                                type: [string, "null"]
                                description: 翻訳実行が0件の場合は未計測のnull。
                          cached:
                            type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  # ============================================================================
  # Internal / 観測系(#1105 Phase B)
  # ============================================================================

  /api/health/db:
    get:
      summary: DB ヘルスチェック(運用者向け)
      description: |
        Postgres / Supabase の実接続チェックを返す。
        本番では `Authorization: Bearer ${HEALTH_CHECK_SECRET}` 必須(未提示時は admin のみ許可)。
        本番でトークンも admin もない場合は 403 (fail-closed)。
      operationId: getHealthDb
      x-audience: internal
      x-auth: bearer_token
      tags:
        - Health
      security:
        - HealthCheckSecretAuth: []
      responses:
        '200':
          description: DB 正常
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '403':
          description: 未認証または HEALTH_CHECK_SECRET 未設定(fail-closed)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: DB 異常
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true

  /api/health/auth:
    get:
      summary: 認証システムヘルスチェック(運用者向け)
      description: |
        認証コンポーネント(Supabase Auth, JWT 等)の設定確認エンドポイント。
        本番では `Authorization: Bearer ${HEALTH_CHECK_SECRET}` または admin 必須。
      operationId: getHealthAuth
      x-audience: internal
      x-auth: bearer_token
      tags:
        - Health
      security:
        - HealthCheckSecretAuth: []
      responses:
        '200':
          description: 認証システム正常
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '403':
          description: 未認証
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: 認証システム異常
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true

  /api/security/stats:
    get:
      summary: セキュリティ統計(管理者用)
      description: |
        Rate Limit / IP block / 異常検知などのセキュリティ統計を返す。
        SSOT: `src/lib/security/security-metrics.ts`。
      operationId: getSecurityStats
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      responses:
        '200':
          description: 統計取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/security/token-stats:
    get:
      summary: トークン統計(管理者用)
      description: |
        JWT / refresh token の発行・失効・回転状況を集計する。
        SSOT: `src/lib/security/jti-tracker.ts`, `token-rotation.ts`。
      operationId: getSecurityTokenStats
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      responses:
        '200':
          description: トークン統計取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      summary: トークン統計クリア
      description: 管理者向け運用操作。トークン統計のクリア・リセット。
      operationId: postSecurityTokenStats
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      responses:
        '200':
          description: 操作成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/translate/metrics:
    get:
      summary: 翻訳メトリクス(管理者用)
      description: |
        詳細な翻訳性能メトリクス収集は停止中。全variantで collectionStatus=paused、
        measured=false とし、測定値は null を返す。0 を健全な実測値として扱わない。
        public ユーザーには公開しない管理者専用エンドポイント。
      operationId: getTranslateMetrics
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      responses:
        '200':
          description: 翻訳メトリクス取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        oneOf:
                          - type: object
                            required: [type, fileId, collectionStatus, measured, metrics]
                            properties:
                              type:
                                type: string
                                const: file
                              fileId:
                                type: string
                              collectionStatus:
                                type: string
                                const: paused
                              measured:
                                type: boolean
                                const: false
                              metrics:
                                type: "null"
                          - type: object
                            required: [type, period, collectionStatus, measured, metrics]
                            properties:
                              type:
                                type: string
                                const: realtime
                              period:
                                type: string
                                const: 1h
                              collectionStatus:
                                type: string
                                const: paused
                              measured:
                                type: boolean
                                const: false
                              metrics:
                                type: "null"
                          - type: object
                            required:
                              - type
                              - period
                              - collectionStatus
                              - measured
                              - totals
                              - averages
                              - dailyMetrics
                            properties:
                              type:
                                type: string
                                const: summary
                              period:
                                type: string
                                description: Requested day count followed by d.
                              collectionStatus:
                                type: string
                                const: paused
                              measured:
                                type: boolean
                                const: false
                              totals:
                                type: "null"
                              averages:
                                type: "null"
                              dailyMetrics:
                                type: "null"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/metrics/cache:
    get:
      summary: 翻訳キャッシュメトリクス(管理者用)
      description: |
        翻訳キャッシュ(RPC v2)のヒット率・命中件数・失効件数等を返す。
      operationId: getMetricsCache
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      responses:
        '200':
          description: キャッシュメトリクス取得成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      summary: 翻訳キャッシュ操作(管理者用)
      description: 管理者向けキャッシュ操作(パージ等)。
      operationId: postMetricsCache
      x-audience: admin
      x-auth: admin_session
      tags:
        - Admin
      security:
        - CookieAuth: []
      responses:
        '200':
          description: 操作成功
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiSuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/preview/recovery:
    get:
      summary: プレビュー復旧対象一覧(cron / 監視向け)
      description: |
        失敗 / スタックしたプレビュージョブのリストを返す。
        staging / production は Cloud Scheduler OIDC(`SCHEDULER_SA_EMAILS` allowlist)必須。
        local / test は OIDC または `x-cron-secret` で許可。
        詳細: `docs/preview-system.md`。
      operationId: getPreviewRecovery
      x-audience: internal
      x-auth: oidc
      tags:
        - Preview
      security: []
      responses:
        '200':
          description: 対象ジョブ一覧取得成功
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      summary: プレビュー復旧バッチ実行(cron)
      description: |
        失敗 / スタックジョブの再キックを実行する。Cloud Scheduler から定期実行される想定。
      operationId: postPreviewRecovery
      x-audience: internal
      x-auth: oidc
      tags:
        - Preview
      security: []
      responses:
        '200':
          description: 復旧処理実行成功
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/internal/health:
    get:
      summary: 内部ヘルスチェック(認証必須)
      description: |
        DB/Storage の実接続チェックを含む詳細なヘルスチェック。
        Service Role Key を使用するため、Bearer token(HEALTH_CHECK_SECRET)で保護。
        用途: 本番環境の深いヘルスモニタリング(Vercel cron / 内部監視)。
      operationId: internalHealthCheck
      x-audience: internal
      x-auth: bearer_token
      tags:
        - Health
      security:
        - HealthCheckSecretAuth: []
      responses:
        '200':
          description: 全コンポーネント正常
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InternalHealthResponse'
        '403':
          description: 未認証または HEALTH_CHECK_SECRET 未設定(fail-closed)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        '503':
          description: 一部コンポーネント異常
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InternalHealthResponse'

components:
  securitySchemes:
    # NOTE (#1089 / #1094 follow-up): Cookie session を SSOT として CookieAuth に統一。
    # 旧 BearerAuth 記述は 18 endpoint 全てを CookieAuth に移行済み。
    # 新規エンドポイントは必ず CookieAuth を使用すること。
    CookieAuth:
      type: apiKey
      in: cookie
      name: sb-access-token
      description: |
        Supabase Auth の Cookie session(HttpOnly + Secure + SameSite=Lax)。
        実装上の cookie 名は project ref に依存する
        (例: `sb-<project-ref>-auth-token`) が、論理的に本スキームで表現する。

    HealthCheckSecretAuth:
      type: http
      scheme: bearer
      description: HEALTH_CHECK_SECRET 環境変数による内部ヘルスチェック認証(Supabase JWT とは別)

  parameters:
    TranslationJobId:
      name: jobId
      in: path
      required: true
      schema: { type: string, format: uuid }

  schemas:
    # ============================================================================
    # 基本型定義
    # ============================================================================

    TranslationJobItem:
      type: object
      additionalProperties: false
      required: [item_key, original_text]
      properties:
        item_key: { type: string, minLength: 1, maxLength: 255 }
        original_text: { type: string, minLength: 1, maxLength: 20000 }
        slide_number: { type: integer, minimum: 0 }
        element_index: { type: integer, minimum: 0 }

    TranslationJobOwnedManifest:
      description: Server-owned manifest derived from the file extracted_data. Create requests do not accept an items body; this schema documents the execution cap.
      type: array
      minItems: 1
      maxItems: 5000
      items:
        $ref: '#/components/schemas/TranslationJobItem'

    TranslationJobCreateRequest:
      type: object
      additionalProperties: false
      required: [fileId, targetLanguage]
      properties:
        fileId: { type: string, format: uuid }
        targetLanguage: { type: string, pattern: '^[a-z]{2}(-[A-Z]{2})?$' }
        sourceLanguage: { type: string, pattern: '^[a-z]{2}(-[A-Z]{2})?$' }
        options:
          type: object
          additionalProperties: false
          properties:
            customInstruction: { type: string, maxLength: 100000 }
            glossary:
              type: object
              additionalProperties: { type: string, maxLength: 500 }
            includeSpeakerNotes: { type: boolean }
            qualityTier: { type: string, enum: [standard, high] }
            sourceLang: { type: string, pattern: '^[a-z]{2}(-[A-Z]{2})?$' }
            style: { type: string, enum: [standard, business, technical, casual] }

    TranslationJobResponse:
      type: object
      additionalProperties: false
      required: [id, fileId, status, targetLanguage, totalItems, completedItems, failedItems, pagesTranslated, errorCode, revisionId, createdAt, updatedAt, completedAt, cancelledAt]
      properties:
        id: { type: string, format: uuid }
        fileId: { type: string, format: uuid }
        status: { type: string, enum: [queued, running, ready_to_finalize, finalizing, completed, completed_with_errors, failed, cancelled] }
        targetLanguage: { type: string }
        totalItems: { type: integer, minimum: 0 }
        completedItems: { type: integer, minimum: 0 }
        failedItems: { type: integer, minimum: 0 }
        pagesTranslated: { type: integer, minimum: 0 }
        errorCode: { type: [string, 'null'] }
        revisionId: { type: [string, 'null'], format: uuid }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
        completedAt: { type: [string, 'null'], format: date-time }
        cancelledAt: { type: [string, 'null'], format: date-time }

    TranslationJobTerminalNoticeClaim:
      oneOf:
        - type: object
          additionalProperties: false
          required: [claimed, status]
          properties:
            claimed: { const: false }
            status: { type: 'null' }
        - type: object
          additionalProperties: false
          required: [claimed, status]
          properties:
            claimed: { const: true }
            status: { type: string, enum: [completed, completed_with_errors, failed, cancelled] }

    QualityMetrics:
      type: object
      additionalProperties: false
      required:
        - cohortBasis
        - downloadUrlIssuanceRate
        - editCohortBasis
        - retranslationRate
        - editFileRate
        - editElementRate
        - editObservationPeriod
        - editReasonCounts
        - totalTranslations
        - totalDownloadUrlsIssued
        - period
      properties:
        cohortBasis:
          type: string
          enum: [file_created_at]
          description: downloadUrlIssuanceRateとtotalTranslationsのcompletedファイルコホートを期間抽出する時刻列。現行はファイル作成日時
        downloadUrlIssuanceRate:
          type: number
          minimum: 0
          maximum: 100
          description: 期間内作成かつ現在completedのファイルのうち、同期間内に1回以上署名ダウンロードURLが発行されたファイルの割合。Storage取得完了率ではない
        editCohortBasis:
          type: string
          enum: [file_completed_at]
          description: 編集機会の開始を表すファイル翻訳完了日時を編集率コホートの基準とする
        retranslationRate:
          $ref: '#/components/schemas/RetranslationRateMeasurement'
          description: 翻訳operation種別の計測開始前後を明示する再翻訳率
        editFileRate:
          $ref: '#/components/schemas/EditRateMeasurement'
          description: 編集されたファイル数を翻訳完了ファイル数で割った割合
        editElementRate:
          $ref: '#/components/schemas/EditRateMeasurement'
          description: 編集された翻訳要素数を翻訳完了要素数で割った割合
        editObservationPeriod:
          $ref: '#/components/schemas/EditObservationPeriod'
        editReasonCounts:
          $ref: '#/components/schemas/EditReasonCounts'
        totalTranslations:
          type: integer
          minimum: 0
          description: 期間内に作成され、現在completedのファイル数
        totalDownloadUrlsIssued:
          type: integer
          minimum: 0
          description: 期間内の署名ダウンロードURL発行イベント総数(同一ファイルの重複を含む)。Storage取得完了数ではない
        period:
          type: object
          additionalProperties: false
          required: [start, end]
          properties:
            start:
              type: string
              format: date-time
            end:
              type: string
              format: date-time

    EditRateMeasurement:
      description: 編集履歴計測の開始前後を明示する測定値
      oneOf:
        - type: object
          additionalProperties: false
          required: [status, value, reason, numerator, denominator]
          properties:
            status:
              type: string
              enum: [available]
            value:
              type: number
              minimum: 0
              maximum: 100
            reason:
              type: "null"
            numerator:
              type: integer
              minimum: 0
            denominator:
              type: integer
              minimum: 1
        - type: object
          additionalProperties: false
          required: [status, value, reason]
          properties:
            status:
              type: string
              enum: [unavailable]
            value:
              type: "null"
            reason:
              type: string
              enum: [translation_edit_tracking_not_started, no_eligible_cohort]

    EditObservationPeriod:
      type: object
      additionalProperties: false
      required: [requestedStart, effectiveStart, end, trackingStartedAt, coverageStatus]
      properties:
        requestedStart:
          type: string
          format: date-time
        effectiveStart:
          type: [string, "null"]
          format: date-time
        end:
          type: string
          format: date-time
        trackingStartedAt:
          type: [string, "null"]
          format: date-time
        coverageStatus:
          oneOf:
            - type: string
              enum: [full, tracking_started_during_period, retention_truncated]
            - type: "null"

    EditReasonCounts:
      type: object
      additionalProperties: false
      required: [manual_edit, ai_refine_accept, overflow_shorten_accept, unclassified]
      properties:
        manual_edit:
          type: integer
          minimum: 0
        ai_refine_accept:
          type: integer
          minimum: 0
        overflow_shorten_accept:
          type: integer
          minimum: 0
        unclassified:
          type: integer
          minimum: 0

    RetranslationRateMeasurement:
      description: 翻訳operation種別の計測開始前後を明示する測定値
      oneOf:
        - type: object
          additionalProperties: false
          required: [status, value, reason]
          properties:
            status:
              type: string
              enum: [available]
            value:
              type: number
              minimum: 0
              maximum: 100
            reason:
              type: "null"
        - type: object
          additionalProperties: false
          required: [status, value, reason]
          properties:
            status:
              type: string
              enum: [unavailable]
            value:
              type: "null"
            reason:
              type: string
              enum: [translation_operation_tracking_not_started]

    TargetLanguage:
      type: string
      description: "翻訳対象言語コード(BCP 47)。SSOT: config/languages.yaml"
      enum:
        - ja
        - en
        - zh
        - zh-TW
        - ko
        - es
        - fr
        - de
        - it
        - pt
        - ru
        - ar
        - hi
        - th
        - vi

    # ============================================================================
    # 翻訳関連スキーマ
    # ============================================================================
    
    TextToTranslate:
      type: object
      description: 翻訳リクエスト用のテキスト
      required:
        - id
        - text
      properties:
        id:
          type: string
          description: テキスト要素のID
        text:
          type: string
          description: 翻訳するテキスト
        context:
          type: string
          description: テーブル等のコンテキスト情報

    TranslatedTextResult:
      type: object
      description: 翻訳結果
      required:
        - id
        - original
        - translated
      properties:
        id:
          type: string
          description: テキスト要素のID
        original:
          type: string
          description: 元のテキスト
        translated:
          type: string
          description: 翻訳されたテキスト
        confidence:
          type: number
          description: 翻訳の信頼度(0-1)
          minimum: 0
          maximum: 1

    SingleTranslationRequest:
      type: object
      description: 単一テキスト翻訳リクエスト(/api/translate/single 用)
      required:
        - fileId
        - text
        - textId
        - slideIndex
        - targetLanguage
      properties:
        fileId:
          type: string
          format: uuid
          description: 翻訳対象ファイルのID
        text:
          type: string
          description: 翻訳するテキスト
        textId:
          type: string
          description: テキスト要素のID
        slideIndex:
          type: integer
          minimum: 0
          description: スライドインデックス(0始まり)
        targetLanguage:
          $ref: '#/components/schemas/TargetLanguage'
        sourceLanguage:
          type: string
          description: "ソース言語コード(オプション)"
        priority:
          type: string
          enum: [INTERACTIVE, BACKGROUND, BATCH]
          description: 翻訳優先度

    SingleTranslationResponse:
      type: object
      description: 単一テキスト翻訳レスポンス
      required:
        - success
      properties:
        success:
          type: boolean
        translatedText:
          type: string
          description: 翻訳結果テキスト
        fromCache:
          type: boolean
          description: キャッシュから取得したかどうか
        errorCode:
          type: string
          description: エラーコード(エラー時)
        retryAfter:
          type: integer
          minimum: 0
          description: 再試行可能になるまでの秒数(エラー時)

    TranslationRequest:
      type: object
      description: ストリーミング翻訳リクエスト(/api/translate/stream 用)
      required:
        - texts
        - targetLanguage
      properties:
        texts:
          type: array
          description: 翻訳するテキストの配列
          items:
            $ref: '#/components/schemas/TextToTranslate'
        targetLanguage:
          $ref: '#/components/schemas/TargetLanguage'
        sourceLanguage:
          type: string
          description: "ソース言語コード(オプション、自動検出の場合は \"auto\")"

    # ============================================================================
    # API リクエスト/レスポンススキーマ
    # ============================================================================
    
    SuccessResponse:
      type: object
      description: 成功レスポンス
      required:
        - success
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
          description: メッセージ

    HealthResponse:
      type: object
      description: ヘルスチェックレスポンス
      required:
        - status
      properties:
        status:
          type: string
          enum:
            - ok
            - error
          description: システムの状態
        timestamp:
          type: string
          format: date-time
          description: チェック時刻
        version:
          type: string
          description: APIバージョン

    # ============================================================================
    # Preview API スキーマ
    # ============================================================================

    PreviewJobStatus:
      type: string
      description: プレビュージョブの状態
      enum:
        - queued
        - pdf_generating
        - pdf_ready
        - thumbs_running
        - completed
        - completed_with_errors
        - failed

    PreviewPageStatus:
      type: string
      description: プレビューページの状態
      enum:
        - pending
        - done
        - failed

    PreviewPage:
      type: object
      description: プレビューページ情報
      required:
        - page_index
        - status
      properties:
        page_index:
          type: integer
          description: ページインデックス(0始まり)
          minimum: 0
        status:
          $ref: '#/components/schemas/PreviewPageStatus'
        width:
          type: integer
          description: ページ幅(ピクセル)
        height:
          type: integer
          description: ページ高さ(ピクセル)
        thumb_path:
          type: string
          description: サムネイルのStorage内パス

    PreviewStartRequest:
      type: object
      description: プレビュージョブ開始リクエスト
      required:
        - fileId
      properties:
        fileId:
          type: string
          format: uuid
          description: 対象ファイルのID

    PreviewStartResponse:
      type: object
      description: プレビュージョブ開始レスポンス
      required:
        - jobId
      properties:
        jobId:
          type: string
          format: uuid
          description: プレビュージョブID
        existing:
          type: boolean
          description: 既存ジョブを返却した場合true

    PreviewStatusResponse:
      type: object
      description: プレビュージョブステータスレスポンス
      required:
        - status
      properties:
        status:
          $ref: '#/components/schemas/PreviewJobStatus'
        totalPages:
          type: integer
          description: 総ページ数
          minimum: 0
        thumbsDone:
          type: integer
          description: サムネイル生成完了数
          minimum: 0
        thumbsFailed:
          type: integer
          description: サムネイル生成失敗数
          minimum: 0
        pdfPath:
          type: string
          description: PDFのStorage内パス
        pages:
          type: array
          description: ページ情報の配列
          items:
            $ref: '#/components/schemas/PreviewPage'
        error:
          type: string
          description: エラーメッセージ(失敗時)

    PreviewSignedUrlResponse:
      type: object
      description: 署名付きURL取得レスポンス
      required:
        - url
      properties:
        url:
          type: string
          format: uri
          description: 署名付きURL(1時間有効)

    PreviewThumbsStartRequest:
      type: object
      description: サムネイル生成開始リクエスト
      required:
        - jobId
      properties:
        jobId:
          type: string
          format: uuid
          description: プレビュージョブID

    PreviewThumbsStartResponse:
      type: object
      description: サムネイル生成開始レスポンス
      properties:
        success:
          type: boolean
          description: 処理成功フラグ
        message:
          type: string
          description: メッセージ

    PreviewThumbsRetryRequest:
      type: object
      description: 失敗ページ一括再生成リクエスト
      required:
        - jobId
      properties:
        jobId:
          type: string
          format: uuid
          description: プレビュージョブID

    PreviewThumbsRetryResponse:
      type: object
      description: 失敗ページ一括再生成レスポンス
      properties:
        retriedCount:
          type: integer
          description: 再生成を試みたページ数
          minimum: 0
        message:
          type: string
          description: メッセージ

    PreviewThumbsRetryPageRequest:
      type: object
      description: 単一ページ再生成リクエスト
      required:
        - jobId
        - pageIndex
      properties:
        jobId:
          type: string
          format: uuid
          description: プレビュージョブID
        pageIndex:
          type: integer
          description: ページインデックス(0始まり)
          minimum: 0

    PreviewThumbsRetryPageResponse:
      type: object
      description: 単一ページ再生成レスポンス
      properties:
        success:
          type: boolean
          description: 処理成功フラグ
        message:
          type: string
          description: メッセージ

    # ============================================================================
    # Usage / Error Report / Internal Health スキーマ
    # ============================================================================

    UsageCheckResponse:
      type: object
      description: 使用量チェックレスポンス
      required:
        - canTranslate
        - remainingFiles
      properties:
        canTranslate:
          type: boolean
          description: 翻訳可能かどうか
        remainingFiles:
          type: integer
          description: 残り翻訳可能ファイル数
          minimum: 0
        currentUsage:
          type: integer
          description: 今月の使用ファイル数
        monthlyLimit:
          type: integer
          description: 月間制限ファイル数
        plan:
          type: string
          description: 現在のプラン名

    ErrorReportRequest:
      type: object
      description: クライアントエラーレポートリクエスト
      properties:
        error:
          type: string
          description: エラーメッセージ
        message:
          type: string
          description: エラーメッセージ(error-boundary-wrapper 互換)
        errorInfo:
          type: object
          properties:
            componentStack:
              type: string
            stack:
              type: string
            type:
              type: string
            url:
              type: string
        stack:
          type: string
          description: スタックトレース(フラットペイロード互換)
        url:
          type: string
          description: エラー発生URL(フラットペイロード互換)
        userAgent:
          type: string
        timestamp:
          type: string

    InternalHealthResponse:
      type: object
      description: 内部ヘルスチェックレスポンス
      required:
        - status
        - timestamp
        - checks
        - test_mode_enabled
      properties:
        status:
          type: string
          enum: [healthy, unhealthy]
        timestamp:
          type: string
          format: date-time
        test_mode_enabled:
          type: boolean
          description: |
            production runtime では常に false。
            true が返った場合は env 設定不備(Vercel に E2E_* が残存等)または production 判定失敗を示す。
            #1128: post-deploy probe で本フィールドを監視し、true なら deploy job を fail させる。
        checks:
          type: object
          properties:
            database:
              $ref: '#/components/schemas/HealthCheckResult'
            storage:
              $ref: '#/components/schemas/HealthCheckResult'
            rpc:
              $ref: '#/components/schemas/HealthCheckResult'
            anthropic:
              $ref: '#/components/schemas/HealthCheckResult'
            stripe:
              $ref: '#/components/schemas/HealthCheckResult'

    HealthCheckResult:
      type: object
      description: 個別コンポーネントのヘルスチェック結果
      required:
        - status
      properties:
        status:
          type: string
          enum: [healthy, unhealthy, configured, disabled, missing, partial]
          description: |
            stripe の disabled は Stripe の 4 つの env が全部ない無料公開で、正常な状態。
            partial は一部だけ設定された不正な状態で、unhealthy として扱う。
        latency:
          type: integer
          description: レイテンシ(ミリ秒)

    # ============================================================================
    # エラーレスポンススキーマ
    # ============================================================================

    ErrorResponse:
      type: object
      description: エラーレスポンス
      required:
        - error
      properties:
        error:
          type: object
          required:
            - type
            - message
            - timestamp
          properties:
            type:
              type: string
              enum:
                - VALIDATION_ERROR
                - AUTHENTICATION_ERROR
                - AUTHORIZATION_ERROR
                - NOT_FOUND
                - CONFLICT
                - RATE_LIMIT_ERROR
                - EXTERNAL_SERVICE_ERROR
                - DATABASE_ERROR
                - FILE_PROCESSING_ERROR
                - INTERNAL_SERVER_ERROR
              description: エラータイプ
            message:
              type: string
              description: エラーメッセージ
            code:
              type: string
              description: エラーコード(オプション)
            details:
              type: object
              description: エラーの詳細情報(オプション)
              additionalProperties: true
            timestamp:
              type: string
              format: date-time
              description: エラー発生時刻
            requestId:
              type: string
              format: uuid
              description: リクエストID(デバッグ用)

    # ============================================================================
    # 共通レスポンスエンベロープ(src/lib/api/response-builder.ts と整合)
    # ============================================================================

    ApiSuccessEnvelope:
      type: object
      description: |
        `createApiSuccessResponse()` が返す共通成功レスポンスエンベロープ。
        個別エンドポイントは `allOf` で `data` プロパティを上書きする。
      required:
        - success
        - status
        - data
        - timestamp
        - requestId
      properties:
        success:
          type: boolean
          enum: [true]
        status:
          type: integer
          description: HTTP ステータスコード
        data:
          description: ペイロード本体(個別エンドポイントで型を上書き)
        timestamp:
          type: string
          format: date-time
        requestId:
          type: string
          format: uuid
        metadata:
          type: object
          additionalProperties: true
          description: 追加メタデータ(オプション)

    # ============================================================================
    # Admin API スキーマ (#1100 / #1105 follow-up)
    # ============================================================================
    # SSOT: src/types/admin.ts (AdminOverviewResponse 等)。
    # 型同期は CI 強制ではないため、admin.ts の変更時は本ファイルも更新すること。

    AdminOverviewStats:
      type: object
      description: 管理者ダッシュボードの集計値
      required:
        - totalUsers
        - adminUsers
        - totalFiles
        - completedFiles
        - totalStorageUsed
        - monthlyUploads
        - monthlyTranslations
        - successRate
      properties:
        totalUsers:
          type: integer
        adminUsers:
          type: integer
        totalFiles:
          type: integer
        completedFiles:
          type: integer
        totalStorageUsed:
          type: integer
          description: 累計ストレージ使用量(bytes)
        monthlyUploads:
          type: integer
          description: 当月のアップロード件数
        monthlyTranslations:
          type: integer
          description: 当月の翻訳件数
        successRate:
          type: number
          description: 翻訳成功率 (0-100)

    AdminUser:
      type: object
      description: 管理者ダッシュボード向けユーザー情報
      required:
        - id
        - username
        - role
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        username:
          type: string
        full_name:
          type: string
        role:
          type: string
          enum: [user, admin]
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        fileCount:
          type: integer
        storageUsed:
          type: integer

    AdminActivity:
      type: object
      description: アクティビティログエントリ
      required:
        - id
        - user_id
        - action
        - created_at
      properties:
        id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        action:
          type: string
        description:
          type: [string, "null"]
        metadata:
          description: 任意の構造化メタデータ
        ip_address:
          type: [string, "null"]
        user_agent:
          type: [string, "null"]
        created_at:
          type: string
          format: date-time
        user:
          type: [object, "null"]
          properties:
            username:
              type: string
            full_name:
              type: string

    AdminUsageStat:
      type: object
      description: 利用統計の日次集計
      required:
        - date
        - uploads
        - translations
        - activeUsers
      properties:
        date:
          type: string
        uploads:
          type: integer
        translations:
          type: integer
        activeUsers:
          type: integer

    AdminOverviewResponse:
      type: object
      description: 管理者ダッシュボード概要レスポンス
      required:
        - stats
        - users
        - activities
        - usage_stats
      properties:
        stats:
          $ref: '#/components/schemas/AdminOverviewStats'
        users:
          type: array
          items:
            $ref: '#/components/schemas/AdminUser'
        activities:
          type: array
          items:
            $ref: '#/components/schemas/AdminActivity'
        usage_stats:
          type: array
          items:
            $ref: '#/components/schemas/AdminUsageStat'

  responses:
    BadRequest:
      description: リクエストが不正です
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: VALIDATION_ERROR
              message: ファイル情報が不足しています
              timestamp: '2024-01-01T00:00:00Z'
              requestId: '123e4567-e89b-12d3-a456-426614174000'

    Unauthorized:
      description: 認証が必要です
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: AUTHENTICATION_ERROR
              message: 認証が必要です
              timestamp: '2024-01-01T00:00:00Z'
              requestId: '123e4567-e89b-12d3-a456-426614174000'

    Forbidden:
      description: アクセス権限がありません
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: AUTHORIZATION_ERROR
              message: このファイルを削除する権限がありません
              timestamp: '2024-01-01T00:00:00Z'
              requestId: '123e4567-e89b-12d3-a456-426614174000'

    Conflict:
      description: 現在の状態または冪等性キーと競合しています
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

    NotFound:
      description: リソースが見つかりません
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: NOT_FOUND
              message: ファイルが見つかりません
              timestamp: '2024-01-01T00:00:00Z'
              requestId: '123e4567-e89b-12d3-a456-426614174000'

    TooManyRequests:
      description: レート制限を超えました
      headers:
        Retry-After:
          description: 再試行可能なまでの秒数
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: RATE_LIMIT_ERROR
              message: レート制限を超えました
              timestamp: '2024-01-01T00:00:00Z'
              requestId: '123e4567-e89b-12d3-a456-426614174000'
              details:
                retryAfter: 60

    InternalServerError:
      description: サーバー内部エラー
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: INTERNAL_SERVER_ERROR
              message: サーバーエラー
              timestamp: '2024-01-01T00:00:00Z'
              requestId: '123e4567-e89b-12d3-a456-426614174000'

Error Responses

The API uses standard HTTP status codes and error response formats.

Error Response Format

{
  "error": {
    "type": "ERROR_TYPE",
    "message": "Error message",
    "code": "ERROR_CODE",
    "timestamp": "2024-01-01T00:00:00Z",
    "requestId": "uuid"
  }
}

Main Error Types

  • VALIDATION_ERROR: Request validation error (400)
  • AUTHENTICATION_ERROR: Authentication error (401)
  • AUTHORIZATION_ERROR: Authorization error (403)
  • NOT_FOUND: Resource not found (404)
  • RATE_LIMIT_ERROR: Rate limit exceeded (429)
  • INTERNAL_SERVER_ERROR: Internal server error (500)