> ## Documentation Index
> Fetch the complete documentation index at: https://developer.box.com/llms.txt
> Use this file to discover all available pages before exploring further.

> 重要なドキュメントのアップロード、レンダリング、レビューのすべてを、自分のアプリ内で行います。

# Box Viewを使用した安全なアプリ内ドキュメントレビューの構築

export const RelatedLinks = ({title, items = []}) => {
  const getBadgeClass = badge => {
    if (!badge) return "badge-default";
    const badgeType = badge.toLowerCase().replace(/\s+/g, "-");
    return `badge-${badge === "ガイド" ? "guide" : badgeType}`;
  };
  if (!items || items.length === 0) {
    return null;
  }
  return <div className="my-8">
      {}
      <h3 className="text-sm font-bold uppercase tracking-wider mb-4">{title}</h3>

      {}
      <div className="flex flex-col gap-3">
        {items.map((item, index) => <a key={index} href={item.href} className="py-2 px-3 rounded related_link hover:bg-[#f2f2f2] dark:hover:bg-[#111827] flex items-center gap-3 group no-underline hover:no-underline border-b-0">
            {}
            <span className={`px-2 py-1 rounded-full text-xs font-semibold uppercase tracking-wide flex-shrink-0 ${getBadgeClass(item.badge)}`}>
              {item.badge}
            </span>

            {}
            <span className="text-base">{item.label}</span>
          </a>)}
      </div>
    </div>;
};

export const SignupCTA = ({children}) => {
  return <div className="flex flex-wrap items-center gap-4 p-5 rounded-lg border border-gray-200 dark:border-gray-700 my-6" style={{
    background: "linear-gradient(135deg, rgba(0, 97, 213, 0.06), rgba(0, 97, 213, 0.02))"
  }}>
      <div className="flex-1 text-sm leading-relaxed text-gray-700 dark:text-gray-300" style={{
    minWidth: "280px"
  }}>
        {children}
      </div>
      <div className="flex flex-col items-center gap-2">
        <a href="https://account.box.com/signup/developer#ty9l3" className="signup-cta-button inline-flex items-center whitespace-nowrap px-5 py-2 text-sm font-semibold text-white no-underline">
          {translate("Get started for free")}
        </a>
        <a href="https://account.box.com/developers/console" className="signup-cta-login text-xs text-gray-500 dark:text-gray-400 no-underline whitespace-nowrap">
          {translate("Already have an account? Log in")}
        </a>
      </div>
    </div>;
};

export const Link = ({href, children, className, ...props}) => {
  const localizedHref = localizeLink(href);
  return <a href={localizedHref} className={className} {...props}>
      {children}
    </a>;
};

納税申告書、銀行取引明細書、給与明細書など、機密ドキュメントを読んで一日を過ごす場合、何度もコンテキストを切り替える必要があります。PDFをダウンロードし、それを別のビューアーで開いて、さらに別の場所でマークアップしてから、決定を記録するワークベンチに戻ります。切り替えのたびに、ドキュメントの漏えい、メモの紛失、アプリケーションの遅延が生じる可能性があります。

このチュートリアルでは、レビューの全工程が自分のアプリケーション内で完結します。

1. ローンワークベンチがセキュアなアップロードAPIを通じて、各借入人ドキュメントをBoxにアップロードします。
2. Boxが、アップロードされたドキュメントを忠実度の高いHTML5レンダリングに変換します。
3. お客様のアプリが有効期間の短い埋め込みURLをリクエストして、`<iframe>`に挿入します。

審査担当者は、コンテキスト内でドキュメントを読んだり、ドキュメントに注釈を付けたりします。より充実した機能 (コンテキスト内でのマークアップ、メタデータとアクティビティのサイドバー、またはBox AI Q\&A) が必要な場合は、Box内の基になるファイルに<Link href="/guides/embed/ui-elements/preview">Box Content Preview</Link>をレイヤーとして追加します。

<Card title="作業用サンプルの複製" href="https://github.com/box-community/box-secure-doc-review" icon="github" arrow="true">
  動作するコードで試してみたい場合は、このチュートリアルで作成するアプリの完成版がGitHubにあるので、それを複製し、Boxの資格情報を追加して実行してください。
</Card>

## 構築する内容

このチュートリアルの最後には、次の機能を備えた実用的な統合ソリューションが完成します。

* アップロードAPIを使用して借入人ドキュメントをBoxにアップロードし、プレビューのための自動変換をトリガーします。
* アップロードしたファイルのために、有効期間の短い、有効期限付き埋め込みURLを生成します。
* ワークベンチで`<iframe>`内のドキュメントをレンダリングします。
* ファイルの範囲にダウンスコープされたトークンを発行し、ブラウザで保持される資格情報が常に最小権限になるようにします。
* 素のiframeをBox Content Previewにアップグレードして、審査担当者がコンテキスト内で注釈を付けたり、より充実したレビューUIを使用したりできるようにします。

## 前提条件

始める前に、以下が揃っていることを確認してください。

* 開発者コンソールへのアクセス権限とPlatformアプリを承認する管理者権限があるBoxアカウント (<Link href="https://account.box.com/signup/developer#ty9l3">無料のBox Developerアカウント</Link>は使用可。コンシューマ/個人アカウントは使用不可)。オプションのBox AI Q\&Aレイヤーを利用する場合は、有料の<Link href="https://www.box.com/pricing">Box Enterpriseアカウント</Link>が必須です。
* **クライアント資格情報許可 (CCG)** 認証と**アプリケーションアクセス**が \[**アプリアクセスのみ**] に設定されている、<Link href="/guides/embed/box-view/setup">Box View用に構成されたBox Platformアプリ</Link>。これにより、アプリのサービスアカウントが、アップロードされたすべてのドキュメントの所有者になります。
* Python 3.11以上。
* アプリで以下のスコープが有効になっていること。
  * Boxに格納されているすべてのファイルとフォルダの読み取りと書き込み
* ワークベンチのフロントエンド (`http://127.0.0.1:5000`など) に使用する正確なオリジンを、<Link href="https://cloud.app.box.com/developers/console">開発者コンソール</Link>の**CORS許可リスト**に追加します。**これは、<Link href="/guides/tutorials/secure-in-app-document-review#add-in-context-annotations-with-box-content-preview">Box Content Previewの注釈手順</Link>**に必要な操作です。オリジンが許可リストに追加されるまでContent Previewは失敗し、`403`が返されます。
* Box Enterprise ID (開発者コンソールにあるアカウントアイコンの \[**Enterprise IDをコピー**] で取得できます)。

<Note>
  このチュートリアルでは、**サービスアカウント** (`enterprise_id`) としてのCCG認証を使用します。サービスアカウントはアップロードするファイルを所有するため、それらのファイルをプレビューするのにコラボレーションへの招待が必要ありません。これは、既存の企業コンテンツを扱う統合と比べた場合、大きな簡素化ポイントです。
</Note>

## 仕組み

Box Viewのフローは次の3つの段階で構成されます。

1. **アップロード。**バックエンドがセキュアなアップロードAPIを通じて借入人ドキュメントをBoxにアップロードします。コンテンツはウイルススキャンと256ビット暗号化が適用されて保存されます。
2. **変換。**アップロード時に、ファイルは明瞭かつレスポンシブにレンダリングされるHTML5互換のアセットへと自動的に変換されます。変換はファイルごとに1回実行され、アセットはファイルが保存されている限り保持されます。
3. **埋め込み。**バックエンドがファイルの`expiring_embed_link`をリクエストしてフロントエンドに返します。フロントエンドはそれを`<iframe>`に配置します。

このソリューションでは、以下に示す3つのBox Platform機能を使用します。

| コンポーネント              | 目的                                               | API                                             |
| -------------------- | ------------------------------------------------ | ----------------------------------------------- |
| **アップロードAPI**        | 借入人の納税申告書または明細書をBoxに保存する                         | `POST /2.0/files/content`                       |
| **有効期限付き埋め込みリンク**    | `<iframe>`のために有効期間の短いプレビューURLを生成する               | `GET /2.0/files/:id?fields=expiring_embed_link` |
| **トークン交換 (ダウンスコープ)** | Box Content Previewと注釈のためにファイルスコープの最小権限トークンを発行する | `POST /oauth2/token`                            |

<Warning>
  有効期限付き埋め込みリンクは**約1分間**有効で、生成後すぐに`<iframe>`に挿入するよう想定されています。有効期限付き埋め込みリンクは常に、審査担当者がドキュメントを開いたときにオンデマンドでサーバー側で生成するようにして、保存したり事前に送信したりしないでください。
</Warning>

## 手順

<Steps>
  <Step title="開発環境のセットアップ">
    1. ターミナルを開き、新しいプロジェクトディレクトリを作成します。

    ```bash theme={null}
    mkdir loan-review && cd loan-review
    ```

    2. Pythonの仮想環境を作成してアクティブ化します。

    ```bash theme={null}
    python3 -m venv .venv
    source .venv/bin/activate
    ```

    アクティブ化すると、ターミナルのプロンプトの先頭に`(.venv)`と表示されます。これにより、仮想環境内で作業していることがわかります。

    <Note>
      新しいターミナルウィンドウやタブを開くたびに、プロジェクトディレクトリから`source .venv/bin/activate`を実行して、仮想環境を再アクティブ化する必要があります。コマンドの実行中に`ModuleNotFoundError`が表示される場合、通常、venvがアクティブ化されていないことを意味します。
    </Note>

    3. 必要なパッケージをインストールします。

    ```bash theme={null}
    pip install box-sdk-gen flask python-dotenv
    ```

    4. 資格情報を保存するための`.env`ファイルを作成し、以下の内容を追加します。プレースホルダの値を、Box開発者コンソールで確認した実際の資格情報で置き換えます。

    ```bash theme={null}
    BOX_CLIENT_ID=your_client_id
    BOX_CLIENT_SECRET=your_client_secret
    BOX_ENTERPRISE_ID=your_enterprise_id
    LOAN_DOCS_FOLDER_ID=0
    ```

    <Note>
      `LOAN_DOCS_FOLDER_ID`はデフォルトでは、サービスアカウントのルートフォルダ`0`になっています。これは作業を開始するのに適しています。実際の環境では、専用のフォルダ (ローン申請ごとに1フォルダなど) を作成してそのIDを使用することで、ドキュメントが整理された状態に保たれます。
    </Note>

    <Warning>
      `.env`ファイルはバージョン管理システムにコミットさせないでください。`.env`を`.gitignore`に追加します。
    </Warning>

    <Note>
      **環境変数の理解:** `.env`ファイルには機密情報 (実際の資格情報) が保存されています。Pythonコードは、`os.getenv("VARIABLE_NAME")`を使用してこれらの**名前**を参照することで、その値を読み取ります。たとえば`os.getenv("BOX_CLIENT_ID")`を実行すると、`.env`ファイル内の`BOX_CLIENT_ID=`の隣に保存されている値を検索します。以下の手順でコードをコピーする際は、引用符で囲まれた変数名をそのまま正確に保持してください。実際の資格情報に置き換えないでください。
    </Note>
  </Step>

  <Step title="Boxクライアントの認証">
    プロジェクトディレクトリに`box_client.py`という名前の新しいファイルを作成します。そのファイルを開き、以下のコードを貼り付けます。

    ```python theme={null}
    import os
    from dotenv import load_dotenv
    from box_sdk_gen import (
        BoxClient,
        BoxCCGAuth,
        CCGConfig,
    )

    load_dotenv()

    def get_box_client() -> BoxClient:
        config = CCGConfig(
            client_id=os.getenv("BOX_CLIENT_ID"),
            client_secret=os.getenv("BOX_CLIENT_SECRET"),
            enterprise_id=os.getenv("BOX_ENTERPRISE_ID"),
        )
        auth = BoxCCGAuth(config=config)
        return BoxClient(auth=auth)
    ```

    <Tip>
      アプリケーションのサービスアカウントがコンテンツを所有する、サーバー間のBox Viewユースケースでは、クライアント資格情報許可をお勧めします。その他の認証オプションについては、<Link href="/guides/authentication/select">認証方法の選択</Link>を参照してください。
    </Tip>
  </Step>

  <Step title="借入人ドキュメントのアップロード">
    `upload.py`という新しいファイルを作成します。次の関数は、ドキュメントをローンドキュメント用フォルダにアップロードし、新しいファイルIDを返します。ドキュメントをアップロードすると自動的に変換がトリガーされるため、少しするとファイルのプレビュー準備が整います。

    ```python theme={null}
    import os
    from dotenv import load_dotenv
    from box_sdk_gen import (
        BoxClient,
        UploadFileAttributes,
        UploadFileAttributesParentField,
    )

    load_dotenv()

    def upload_document(client: BoxClient, file_path: str) -> str:
        folder_id = os.getenv("LOAN_DOCS_FOLDER_ID", "0")
        file_name = os.path.basename(file_path)

        with open(file_path, "rb") as file_stream:
            uploaded = client.uploads.upload_file(
                UploadFileAttributes(
                    name=file_name,
                    parent=UploadFileAttributesParentField(id=folder_id),
                ),
                file_stream,
            )

        file_id = uploaded.entries[0].id
        print(f"Uploaded {file_name} -> file ID {file_id}")
        return file_id
    ```

    <Note>
      ほとんどのタイプのドキュメントと画像で、変換はアップロード時に自動的にトリガーされます。動画と3Dファイルの場合、変換は最初のプレビュー時にトリガーされます。いずれにしても、変換はファイルごとに1回のみ実行され、コードで明示的な操作を行う必要はありません。
    </Note>
  </Step>

  <Step title="プレビュー用埋め込みURLの生成">
    `preview.py`という新しいファイルを作成します。`get_embed_url`関数は、Boxにファイルの`expiring_embed_link`を要求し、フロントエンドによって`<iframe>`に配置されるURLを返します。

    ```python theme={null}
    from box_sdk_gen import BoxClient

    def get_embed_url(client: BoxClient, file_id: str) -> str:
        file = client.files.get_file_by_id(
            file_id,
            fields=["expiring_embed_link"],
        )

        if not file.expiring_embed_link:
            raise RuntimeError(
                "No embed link returned. The file may still be converting, "
                "or preview is not available for this file type."
            )

        return file.expiring_embed_link.url
    ```

    <Warning>
      `expiring_embed_link`は、`fields`パラメータを通じてのみリクエストし、すぐに使用してください。リンクは、埋め込みの安全性を確保するために、約1分で有効期限が切れるよう設計されています。
    </Warning>
  </Step>

  <Step title="ブラウザ用にダウンスコープされたトークンの発行">
    素のiframeにはトークンは必要ありません。しかし、Box Content Preview (後で追加) はブラウザで実行されるため、トークンが必要です。フル権限のサービスアカウントトークンを**ダウンスコープされた**ファイルスコープのトークンに交換する関数を`preview.py`に追加します。このトークンは、1つのファイルに対してスコープで許可されている操作のみを実行でき、ブラウザに安心して送信できるように有効期間が短く設定されています。

    ```python theme={null}
    from box_sdk_gen import BoxClient

    PREVIEW_SCOPES = [
        "base_preview",
        "item_preview",
        "annotation_edit",
        "annotation_view_all",
        "item_download",
    ]

    def get_downscoped_token(client: BoxClient, file_id: str) -> dict:
        token = client.auth.downscope_token(
            PREVIEW_SCOPES,
            resource=f"https://api.box.com/2.0/files/{file_id}",
        )
        return {
            "access_token": token.access_token,
            "expires_in": token.expires_in,
        }
    ```

    スコープはレビュー機能に直接マッピングされます。

    | スコープ                  | 許可される操作                            |
    | --------------------- | ---------------------------------- |
    | `base_preview`        | Box Content Previewでのファイルのプレビュー    |
    | `item_preview`        | ファイルのプレビュー (有効期限付き埋め込みリンク)         |
    | `annotation_edit`     | 審査担当者自身による注釈の作成、編集、削除              |
    | `annotation_view_all` | すべての審査担当者によるファイル上の注釈の表示            |
    | `item_download`       | テキスト選択の有効化と注釈のハイライト (およびダウンロード/印刷) |

    <Warning>
      クライアントシークレットまたはアクセストークン全体をクライアント側のコードで公開しないようにしてください。トークンは、ブラウザに送信する前に常にダウンスコープします。詳しくは、<Link href="/guides/embed/box-view/best-practices#downscope-tokens">Box Viewのベストプラクティス</Link>を参照してください。
    </Warning>
  </Step>

  <Step title="バックエンドのエンドポイントの公開">
    `app.py`という新しいファイルを作成します。このFlaskアプリケーションは、ローンワークベンチによって呼び出されるバックエンドです。ドキュメントをアップロードするエンドポイント、オンデマンドで新しい埋め込みURLを取得するエンドポイント、ダウンスコープされたトークンを発行するエンドポイントをそれぞれ1つ公開します。

    ```python theme={null}
    import os
    import tempfile

    from dotenv import load_dotenv
    from flask import Flask, request, jsonify, send_file

    from box_client import get_box_client
    from upload import upload_document
    from preview import get_embed_url, get_downscoped_token

    load_dotenv()
    app = Flask(__name__)

    @app.route("/api/documents", methods=["POST"])
    def upload():
        if "file" not in request.files:
            return jsonify({"error": "no file provided"}), 400

        incoming = request.files["file"]
        with tempfile.NamedTemporaryFile(
            delete=False, suffix=f"_{incoming.filename}"
        ) as tmp:
            incoming.save(tmp.name)
            temp_path = tmp.name

        try:
            client = get_box_client()
            file_id = upload_document(client, temp_path)
        finally:
            os.remove(temp_path)

        return jsonify({"file_id": file_id}), 201

    @app.route("/api/documents/<file_id>/embed", methods=["GET"])
    def embed(file_id):
        client = get_box_client()
        embed_url = get_embed_url(client, file_id)
        return jsonify({"embed_url": embed_url}), 200

    @app.route("/api/documents/<file_id>/token", methods=["GET"])
    def token(file_id):
        client = get_box_client()
        return jsonify(get_downscoped_token(client, file_id)), 200

    @app.route("/review/<file_id>")
    def review_page(file_id):
        return send_file("review.html")

    if __name__ == "__main__":
        app.run(port=5000, debug=True)
    ```

    <Note>
      `/embed`エンドポイントは、リクエストが行われるたびにリンクを新しく作成します。リンクは約1分で有効期限が切れるため、フロントエンドは、このエンドポイントを事前に呼び出すのではなく、審査担当者がドキュメントを開いた瞬間に呼び出す必要があります。
    </Note>

    <Warning>
      **ルートを追加または変更するたびにFlaskを再起動してください。**Flaskは起動時に1回コードを読み込むため、新しく追加されたルート (`/review/<file_id>`など) は、`Ctrl+C`でサーバーを停止して`python app.py`を再度実行するまで`404 Not Found`を返します。`debug=True`を設定すると自動再読み込みが有効になるため、以降の編集は自動的に再読み込みされます。ただし、ルート追加直後の実行は、手動で再起動する必要があります。`debug=True`は、実稼働環境では使用しないでください。
    </Warning>

    <Warning>
      `/review/<file_id>`ルートが**バックエンドと同じオリジンから**レビューページを提供するため、ページの相対的な`fetch("/api/...")`コールはAPIに届きます。`review.html`をダブルクリックで開くことはしないでください。これを`file://`プロトコル経由で開くと、相対的な`fetch`には呼び出すサーバーがなく、iframe `src`は設定されず、ページのヘッダーのみが表示されることになります。`review.html`は次の手順で作成します。
    </Warning>

    この時点でプロジェクトディレクトリには、以下のファイルが含まれていることになります。

    ```
    loan-review/
    ├── .env
    ├── .venv/
    ├── app.py
    ├── box_client.py
    ├── preview.py
    └── upload.py
    ```
  </Step>

  <Step title="iframeでのドキュメントのレンダリング">
    バックエンドが準備できたら、フロントエンドは簡単です。埋め込みURLを取得して、それを`<iframe>`の`src`として設定します。これは安全なアプリ内レビューインターフェースであり、審査担当者はワークベンチを離れずに済みます。

    `review.html`というファイルをプロジェクトのルートに作成します (`app.py`の隣に配置することで、`/review/<file_id>`ルートから提供できます)。ファイルIDはページが自らのURLパスから読み取るため、ハードコーディングは不要です。

    ```html theme={null}
    <!DOCTYPE html>
    <html lang="en-US">
      <head>
        <meta charset="utf-8" />
        <title>Loan document review</title>
        <style>
          body { margin: 0; font-family: system-ui, sans-serif; }
          header { padding: 12px 20px; background: #f5f6f8; border-bottom: 1px solid #e0e0e0; }
          iframe { width: 100%; height: calc(100vh - 50px); border: 0; }
        </style>
      </head>
      <body>
        <header><strong>Loan application 4815</strong> &middot; Borrower tax return</header>
        <iframe id="doc" title="Borrower document"></iframe>

        <script>
          // The file ID is the last segment of the URL, e.g. /review/1234567890
          const fileId = window.location.pathname.split("/").pop();

          async function loadPreview() {
            const res = await fetch(`/api/documents/${fileId}/embed`);
            const { embed_url } = await res.json();
            document.getElementById("doc").src = embed_url;
          }

          loadPreview();
        </script>
      </body>
    </html>
    ```

    <Warning>
      このページには、常に`http://127.0.0.1:5000/review/<file_id>`のバックエンド経由でアクセスしてください。`review.html`をダブルクリックしてアクセスしないでください。ダブルクリックしたファイルは`file://`によって開かれます。この場合、相対的な`fetch("/api/...")`には到達できるサーバーがありません。さらに、iframe `src`は設定されず、ヘッダーのみが表示されることになります。このページとAPIはオリジンが共通している必要があります。
    </Warning>

    <Tip>
      実稼働環境では、ワークベンチアプリとBox向けバックエンドが通常、同じゲートウェイ (同じオリジン) の背後で動作するか、`/api/*`がバックエンドにプロキシされるため、こうした相対的な`fetch`コールは解決されます。
    </Tip>
  </Step>

  <Step title="アップロードおよび埋め込みフローのテスト">
    パイプライン全体をUIへの接続前にコマンドラインから実行できます。

    <Note>
      コマンドを実行する前に、`loan-review`ディレクトリを開いていること、また、仮想環境がアクティブ化されていることを確認します。

      ```bash theme={null}
      cd ~/loan-review
      source .venv/bin/activate
      ```
    </Note>

    **1. バックエンドを開始します。**

    ```bash theme={null}
    python app.py
    ```

    `Running on http://127.0.0.1:5000`と表示されます。このターミナルは起動したままにしておきます。

    **2. ドキュメントをアップロードします。**2つ目のターミナルで、サンプルの納税申告書または明細書 (任意のPDFでかまいません) をアップロードします。

    ```bash theme={null}
    curl -X POST http://127.0.0.1:5000/api/documents \
      -F "file=@sample-tax-return.pdf"
    ```

    ファイルIDが返されます。

    ```json theme={null}
    { "file_id": "1234567890" }
    ```

    **3. このファイルIDの埋め込みURLを取得します。**

    ```bash theme={null}
    curl http://127.0.0.1:5000/api/documents/1234567890/embed
    ```

    ```json theme={null}
    { "embed_url": "https://app.box.com/preview/expiring_embed/gvoct6FE!YT_X1Lau..." }
    ```

    **4. この埋め込みURLをiframeで表示します。**バックエンドがまだ実行されているので、ファイルIDを最後のパス部分として使用し、サーバーを通じてレビューページを開きます。

    ```
    http://127.0.0.1:5000/review/1234567890
    ```

    ドキュメントがiframe内でレンダリングされます。`Loan application 4815`ヘッダーのみが表示される場合は、`review.html`をダブルクリック (`file://`プロトコル) で開いていると考えられます。代わりに、上記のURL経由で開いてください。別の健全性チェックとして、手順3の埋め込みURLを1分以内にブラウザに直接貼り付けるという方法もあります。これを行うと、ドキュメントがレンダリングされ、変換が成功したことが証明されます。
  </Step>
</Steps>

## トラブルシューティング

<AccordionGroup>
  <Accordion title="ModuleNotFoundError: 「...」という名前のモジュールが見つからない">
    仮想環境がアクティブ化されていません。`python`コマンドを実行する前に、プロジェクトディレクトリから`source .venv/bin/activate`を実行してください。新しく開いたターミナルタブは、すべて個別にアクティブ化する必要があります。
  </Accordion>

  <Accordion title="invalid_client: クライアント資格情報が無効である">
    以下のように、`.env`ファイルを確認します。

    * `BOX_CLIENT_ID`および`BOX_CLIENT_SECRET`が、開発者コンソール > 自分のアプリ > \[構成] の値と一致していることを確認します。
    * `BOX_ENTERPRISE_ID`が自分のEnterprise IDであることを確認します (開発者コンソール > アカウントアイコン > \[Enterprise IDをコピー])。
    * アプリの種類がクライアント資格情報許可であり、アプリが承認済みであることを確認します。
  </Accordion>

  <Accordion title="unauthorized_client">
    アプリがまだ管理者によって承認されていません。開発者コンソールで、\[**承認**] タブを開いて \[**確認して送信**] をクリックし、Box管理者に管理コンソールでアプリを承認してもらいます。詳しくは、<Link href="/guides/authorization/platform-app-approval">Platformアプリの承認</Link>を参照してください。
  </Accordion>

  <Accordion title="埋め込みリンクが返されない/expiring_embed_linkがnull">
    変換がまだ進行中の可能性があります。または、ファイルの種類がプレビューをサポートしていません。アップロード後、数秒待ってから再度試してください。また、<Link href="/guides/representations/supported-file-types">サポートされているファイルの種類</Link>を参照して、アップロードしたファイルがサポートされている種類であることを確認してください。
  </Accordion>

  <Accordion title="/review/<file_id>を開いたときに404 Not Foundが返される">
    Flaskに`/review/<file_id>`ルートが登録されていません。これはほとんどの場合、ルートは追加したのにサーバーを再起動しなかったことが理由です。Flaskは起動時に1回コードを読み込みます。`Ctrl+C`でサーバーを停止して、`python app.py`を再度実行してください。さらに、`send_file`がインポートされていること (`from flask import Flask, request, jsonify, send_file`)、また、`app.py`にルートが存在することを確認してください。`debug=True`を設定して実行すると、以降の編集に対して自動再読み込みが有効になります。
  </Accordion>

  <Accordion title="ページにヘッダーのみが表示され、ドキュメントが表示されない">
    `review.html`をバックエンド経由ではなく直接 (`file://`プロトコルを介して) 開いています。ページの相対的な`fetch("/api/documents/.../embed")`コールは、ページがAPIと同じオリジンから提供されている場合にのみ機能します。ファイルをダブルクリックするのではなく、バックエンドを起動して`http://127.0.0.1:5000/review/<file_id>`を開きます。ブラウザの開発者コンソールを開いて確認してください。失敗した`fetch`リクエストが表示されるはずです。
  </Accordion>

  <Accordion title="iframeが空になっているか、iframeに「This shared file is no longer available (この共有ファイルは利用できなくなりました)」と表示される">
    埋め込みリンクの有効期限が切れています。埋め込みリンクは約1分持続します。このリンクはiframe `src`を設定する直前に生成し、審査担当者がドキュメントを開くたびに新しいものをリクエストしてください。
  </Accordion>

  <Accordion title="レビューページは機能しているのに注釈ページで403 (Forbidden) が返される">
    これは、**CORS許可リスト**の問題である場合がほとんどです。iframeレビューページは、BoxでホストされているURLを読み込み、オリジンからBox APIを呼び出すことがないため、CORSなしで機能します。ただし、Box Content Previewは、**ブラウザから直接**`api.box.com`を呼び出します。開発者コンソールでは、繰り返される`GET https://api.box.com/2.0/files/<id>?fields=...`リクエストが`preview.js`を返し、`403`からスローされる様子を確認できます。

    次の手順で修正してください。

    * ページを開くときに使用する**正確な**オリジンを、`http://127.0.0.1:5000`などのスキームやポートも含めてCORS許可リスト (開発者コンソール > アプリ名 > \[**構成**] > \[**CORSドメイン**]) に追加します。なお、`127.0.0.1`と`localhost`は異なるオリジンであり、どちらか使用する方を追加します。保存して1～2分待ち、再起動と再読み込みを行います。
    * それでも`403`する場合、ダウンスコープされたトークンの権限が不足しています。\[**すべてのファイルとフォルダの読み取りと書き込み**] スコープを有効にしてあり、その後に管理コンソールでアプリを**再承認**したことを確認します。
    * `/annotate/<file_id>` URL内のファイルIDが、トークンの発行対象となったファイルと一致していることを確認します。トークンは単一のファイルにダウンスコープされているため、IDの不一致でも`403`が返されます。
  </Accordion>
</AccordionGroup>

## Box Content Previewを使用したコンテキスト内注釈の追加

iframeの埋め込みは、迅速な読み取り専用レビューに最適です。納税申告書上の疑わしい数字のハイライトや、署名漏れを指摘するコメントの追加など、審査担当者が**コンテキスト内でドキュメントをマークアップ**できるようにするには、素のiframeの代わりに、<Link href="/guides/embed/ui-elements/preview">Box Content Preview</Link> UI Elementを使用して同じファイルをレンダリングします。コンテンツプレビューはBoxウェブアプリ内でのレビューを担い、クライアント側のみで動作するため、注釈はワークベンチ内に留まります。

ブラウザでコンテンツプレビューを使用するにはトークンが必要です。ダウンスコープされたファイルスコープのトークン (注釈用のスコープを備えたもの) を返す`/token`エンドポイントをすでに作成したため、フロントエンドにフル権限の資格情報が表示されることはありません。

<Warning>
  **この手順を行う場合、使用するオリジンを事前にCORS許可リストに追加しておく必要があります。** Box Content Previewは、**ブラウザから`api.box.com`に直接**API呼び出しを行います。Boxは、ページのオリジンがアプリのCORS許可リスト (開発者コンソール > アプリ名 > \[**構成**] > \[**CORSドメイン**]) に含まれていない限り、その呼び出しを拒否し、`403 (Forbidden)`を返します。

  ページを開くときに使用する**正確な**オリジンを、`http://127.0.0.1:5000`などのスキームやポートも含めて追加します。`http://127.0.0.1:5000`と`http://localhost:5000`は**異なるオリジン**であることに注意してください。実際に参照する方のオリジン (または両方のオリジン) を許可リストに追加します。変更が反映されるまで、1～2分かかります。
</Warning>

`annotate.html`というファイルをプロジェクトのルートに作成し、iframeページと同様にAPIと同じオリジンからそのファイルを提供するルートを`app.py`に追加します。

```python theme={null}
@app.route("/annotate/<file_id>")
def annotate_page(file_id):
    return send_file("annotate.html")
```

```html theme={null}
<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <title>Loan document review</title>
    <link
      rel="stylesheet"
      href="https://cdn01.boxcdn.net/platform/preview/3.0.0/en-US/preview.css"
    />
    <script src="https://cdn01.boxcdn.net/platform/preview/3.0.0/en-US/preview.js"></script>
    <style>
      .preview-container { height: 100vh; width: 100%; }
    </style>
  </head>
  <body>
    <div class="preview-container"></div>

    <script>
      // The file ID is the last segment of the URL, e.g. /annotate/1234567890
      const fileId = window.location.pathname.split("/").pop();

      async function loadPreview() {
        const res = await fetch(`/api/documents/${fileId}/token`);
        const { access_token } = await res.json();

        const preview = new Box.Preview();
        preview.show(fileId, access_token, {
          container: ".preview-container",
          showAnnotations: true,
        });
      }

      loadPreview();
    </script>
  </body>
</html>
```

iframeページを開いたときと同様、ファイルをダブルクリックではなくバックエンド (`http://127.0.0.1:5000/annotate/<file_id>`) を通じて開きます。

`showAnnotations`が`true`に設定されていて、トークンが注釈用のスコープを備えている場合は、審査担当者にプレビューヘッダーの注釈コントロールが表示されます。注釈はBoxのファイルに対して保存されるため、2人目の審査担当者が同じドキュメントを開くと、1人目の審査担当者のメモが表示されます (`annotation_view_all`が付与されているため)。

<Note>
  Boxの注釈では、**コメントのハイライト**、**ハイライトのみ**、**描画**、**ポイント**という注釈の種類がサポートされています。ポイント注釈はドキュメントと画像の両方で機能し、ハイライトおよび描画による注釈はドキュメントのみで機能します。また、ハイライトによる注釈の場合は、テキストレイヤーを使用可能にするために`item_download`スコープも必要です。詳細なマトリックスや、リアルタイムのV4注釈を有効にする方法については、<Link href="/guides/embed/ui-elements/annotations">注釈ガイド</Link>を参照してください。
</Note>

<Warning>
  許可リストにオリジンを追加した**後も**プレビューで`403`が返される場合は、ダウンスコープされたトークンの権限が不足しています。確実にアプリで \[**すべてのファイルとフォルダの読み取りと書き込み**] スコープを有効に設定し、有効にした後で管理コンソールでアプリを**再承認**してください。CCGスコープの変更は、アプリが再承認されるまで反映されません。<Link href="/guides/embed/ui-elements/scopes">専用スコープ</Link>に関するガイドを参照してください。
</Warning>

## レビューUIを充実させるレイヤーの追加

Box Content Previewを実行すると、同一のファイルでレビュー機能を段階的に充実させることができます。その際、ドキュメントを再アップロードしたり、2つ目のコピーを作成したりする必要はありません。

<AccordionGroup>
  <Accordion title="メタデータとアクティビティのサイドバーを追加">
    コンテンツプレビューUI Elementのサイドバーを使用して、ドキュメントと一緒にファイルの詳細、バージョン、アクティビティフィードを表示します。これにより、複数の審査担当者がドキュメントの履歴を確認して、ページを離れることなくそのドキュメントについて議論することができます。アクティビティフィードとドキュメントビューでともに注釈を有効にして、接続されたレビューワークフローを実現します。詳しくは、<Link href="/guides/embed/ui-elements/sidebar">Content Sidebar</Link>を参照してください。
  </Accordion>

  <Accordion title="プレビューアーでBox AIを使用して質問に回答">
    Box AI for UI Elementsを使用すると、コンテンツプレビューのヘッダーにドキュメントのQ\&Aと要約を直接追加できます。審査担当者が「借入人が報告した年収はいくらですか?」と質問すると、ドキュメントに基づいて引用された回答が得られます。`hasHeader: true`と`contentAnswersProps`を`preview.show()`に渡します。詳しくは、<Link href="/guides/embed/ui-elements/preview#box-ai-for-ui-elements">Box AI for UI Elements</Link>を参照してください。
  </Accordion>

  <Accordion title="申請一式をコレクションとしてレビュー">
    ローン申請のファイルが1つであることはまれです。ファイルIDの`collection`を`preview.show()`に渡すと、審査担当者は、単一のプレビューアー内でナビゲーション用の矢印を使用して納税申告書、W-2、銀行取引明細書を閲覧できます。

    ```js theme={null}
    preview.show(fileId, access_token, {
      container: ".preview-container",
      collection: ["1234567890", "1234567891", "1234567892"],
      showAnnotations: true,
    });
    ```

    コレクション内の各ファイルをカバーするリソースとスコープを備えた、ダウンスコープされたトークンを発行します。
  </Accordion>

  <Accordion title="スコープで機能を調整">
    トークンのスコープは審査担当者のロールに合わせます。たとえば、下級の審査担当者には`base_preview` + `annotation_view_self` + `annotation_edit` (自分のメモのみ閲覧可能) を付与し、上級の融資審査担当者には`annotation_view_all`を付与します。決定が確定した後は、編集スコープを削除して、効果的にドキュメントを読み取り専用にします。詳しくは、<Link href="/guides/embed/ui-elements/preview#scopes">コンテンツプレビューのスコープ</Link>を参照してください。
  </Accordion>
</AccordionGroup>

## 本番環境へのスケーリング

<AccordionGroup>
  <Accordion title="申請ごとにドキュメントを整理">
    すべてをサービスアカウントのルートにアップロードするのではなく、ローン申請ごとにフォルダを作成して、そのIDをアップロードの親として渡します。これにより、ドキュメントがグループ化され、クリーンアップが簡単になるうえに、コンプライアンス対応のための<Link href="/guides/retention-policies/index">リテンションポリシー</Link>をフォルダレベルで適用できます。
  </Accordion>

  <Accordion title="トークンと埋め込みリンクをオンデマンドで更新">
    埋め込みリンクは約1分で有効期限が切れ、ダウンスコープされたトークンも有効期間が短く設定されています。どちらも必要になった時点でサーバー側で生成してください。コンテンツプレビューでは、静的な文字列ではなく<Link href="/guides/embed/ui-elements/preview#token-generator-function">トークン生成関数</Link>を渡します。それにより、プレビューアーは必要になるたびに新しいトークンを取得できます。
  </Accordion>

  <Accordion title="サーバー側で資格情報を保持">
    クライアントシークレット、Enterprise ID、およびアクセストークン全体がブラウザに公開されることがあってはなりません。これらは環境変数かシークレットマネージャに保存し、\[**アプリケーションアクセス**] を \[**アプリアクセスのみ**] に設定したままにして、CORS許可リストをBox Content Previewの実行ドメインに厳密に制限します。詳しくは、<Link href="/guides/embed/box-view/best-practices">Box Viewのベストプラクティス</Link>を参照してください。
  </Accordion>

  <Accordion title="Enterprise Eventでレビューを監査">
    <Link href="/guides/events/enterprise-events/for-enterprise">Enterprise Event</Link>を使用すると、すべてのローンドキュメントを横断して、プレビュー、ダウンロード、注釈アクティビティを追跡できます。これにより、コンプライアンス部門はレビューの担当者、内容、時期についての監査証跡を利用できるようになり、ユーザー向けアプリに追跡機能を組み込まなくて済みます。
  </Accordion>
</AccordionGroup>

## 次の手順

<CardGroup cols={2}>
  <Card title="請求書の取り込み自動化" href="/guides/tutorials/invoice-intake" icon="file-invoice" arrow="true">
    Box AI Extractとメタデータを活用して、買掛金処理を自動化します。
  </Card>

  <Card title="Box Viewの概要" href="/guides/embed/box-view/index" icon="eye" arrow="true">
    アップロード、変換、埋め込みが連携する仕組みについて説明します。
  </Card>
</CardGroup>

<RelatedLinks
  title="関連するガイド"
  items={[
{ label: translate("Box View setup"), href: "/guides/embed/box-view/setup", badge: "GUIDE" },
{ label: translate("Create file preview"), href: "/guides/embed/box-view/create-preview", badge: "GUIDE" },
{ label: translate("Content Preview UI Element"), href: "/guides/embed/ui-elements/preview", badge: "GUIDE" },
{ label: translate("Box Annotations"), href: "/guides/embed/ui-elements/annotations", badge: "GUIDE" },
{ label: translate("Downscope a token"), href: "/guides/authentication/tokens/downscope", badge: "GUIDE" }
]}
/>
