> ## 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.

> HR採用イベントをリッスンし、すべての新規採用者向けに標準化および管理されたBoxワークスペースをプロビジョニングするミドルウェアサービスを、Users、Groups、Folders、Collaborationの各APIを使用して構築します。

# Box APIで新規採用者のオンボーディングを自動化する

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 Link = ({href, children, className, ...props}) => {
  const localizedHref = localizeLink(href);
  return <a href={localizedHref} className={className} {...props}>
      {children}
    </a>;
};

新しい従業員が入社したとき、通常は誰かがBoxウェブアプリを操作してその従業員のフォルダを作成し、適切なリソースを共有して、従業員を適切なチームに追加します。そのような手作業は、時間がかかり、一貫性がなく、ミスが発生しやすいものです。同じロールの2人の新規採用者に異なるフォルダ構造とアクセス権限が付与されることになるため、いずれガバナンスや監査上の問題が生じます。

このチュートリアルでは、手作業を排除するミドルウェアサービスを構築します。HRシステムで`hired`イベントが発生したら、サービスがBoxへの認証を行い、新規採用者向けの標準化された個人用フォルダツリーをプロビジョニングし、適切なグループに適切なレベルでアクセス権限を付与し、必要に応じて新規採用者を各自のワークスペースに招待します。すべての新規採用者に、同一の管理された構造が自動的に付与されます。

## 構築する内容

このチュートリアルの最後には、次のような機能を備えた、動作するPythonサービスが完成します。

* Webhookエンドポイントを介してHR `hired`イベントを受け取ります。
* ITやHRなど、アクセスを制御するガバナンスグループを解決 (または作成) します。
* Folders APIを使用して、新規採用者向けに標準化された個人用フォルダツリーを作成します。
* Collaboration APIを使用して、各ガバナンスグループに適切なフォルダレベルでアクセス権限を付与します。
* 必要に応じて、新規採用者を各自のワークスペースに招待し、初日からアクセスできるようにします。
* 実行の冪等性を確保し、同じイベントが再発生しても重複するフォルダやコラボレーションが作成されないようにします。

## 前提条件

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

* 管理者アクセス権限を備えたBoxアカウント。<Link href="https://www.box.com/pricing">Business以上のすべてのプラン</Link>で使用でき、無料の<Link href="https://account.box.com/signup/n/developer">Box Developerアカウント</Link>でも問題ありません (Platformアプリを自動的に承認するため、管理コンソールの承認手順をスキップできます)。
* **クライアント資格情報許可**認証で構成され、管理コンソールで承認されたBoxアプリケーション。
* Python 3.11以上。
* アプリで以下のスコープが有効になっていること。
  * Boxに格納されているすべてのファイルとフォルダの読み取りと書き込み
  * グループを管理する
  * ユーザーを管理する
* \[**構成**] タブで、アプリの \[**アプリアクセスレベル**] を \[**アプリ + Enterpriseアクセス**] に設定します。デフォルトの \[**アプリアクセスのみ**] では、サービスアカウントからのアクセスが独自のコンテンツのみに制限され、Enterpriseユーザーやグループの作成、管理ができません。
* Box Enterprise ID (このIDを確認するには、[開発者コンソール](https://app.box.com/developers/console)でプロフィールアイコンをクリックするか、管理コンソールの \[**アカウントと請求**] を見てください)。

<Note>
  グループおよびユーザーを作成、管理するには管理者権限が必要です。`enterprise_id`でクライアント資格情報許可を使用すると、アプリがEnterpriseサービスアカウントとして機能します。このサービスアカウントは、アプリの \[**アプリアクセスレベル**] が \[**アプリ + Enterpriseアクセス**] に設定されている場合にのみEnterpriseユーザーおよびグループを管理できます。\[**グループを管理する**] および \[**ユーザーを管理する**] スコープを有効にし、アクセスレベルを変更した後は、その変更を反映させるために、管理者が管理コンソールの \[**統合 > Platformアプリ**] でアプリを再承認する必要があります。
</Note>

## 手順

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

| コンポーネント      | 目的                                                                          | API                                   |
| ------------ | --------------------------------------------------------------------------- | ------------------------------------- |
| **グループ**     | アクセスを制御するガバナンスグループを解決または作成する                                                | `POST /2.0/groups`, `GET /2.0/groups` |
| **フォルダ**     | 標準化された個人用フォルダツリーを作成する                                                       | `POST /2.0/folders`                   |
| **コラボレーション** | グループと新規採用者に適切なレベルのアクセス権限を付与する                                               | `POST /2.0/collaborations`            |
| **ユーザー**     | 必要に応じて新規採用者をEnterpriseグループに追加する ([本番環境へのスケーリング](#scaling-to-production)を参照) | `POST /2.0/group_memberships`         |

<Info>
  Boxコラボレーションはカスケードされます。親フォルダにコラボレーションが追加されると、そのフォルダ内のすべての要素に同じアクセス権限が付与されます。このチュートリアルでは、この仕組みを意図的に利用しています。つまり、ツリー内の上位フォルダには広範囲のアクセス権限を付与し、必要な特定のサブフォルダのみに、より狭い範囲のアクセス権限を付与しています。
</Info>

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

    ```bash theme={null}
    mkdir onboarding-workspace && cd onboarding-workspace
    ```

    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
    ONBOARDING_ROOT_FOLDER_ID=0
    ```

    `ONBOARDING_ROOT_FOLDER_ID`は、各新規採用者のワークスペースが作成されるフォルダです。これを`0`のままにしてサービスアカウントのルート上にワークスペースを作成するか、専用の`New Hires`フォルダのIDを設定します。

    <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>
      エンドユーザーを関与させずにサーバー間の処理を自動化する場合には、クライアント資格情報許可をお勧めします。組織内でJWTが必要な場合は、`CCGConfig`/`BoxCCGAuth`を`JWTConfig`/`BoxJWTAuth`と入れ替えます。チュートリアルのコードの他の部分は変更しません。両方の比較については、<Link href="/guides/authentication/select">認証方法の選択</Link>を参照してください。
    </Tip>
  </Step>

  <Step title="ワークスペーステンプレートを定義する">
    管理されたオンボーディングプロセスで最も重要なのは、すべての新規採用者のワークスペースの見た目と、その各部にアクセスできるユーザーが誰であるかを、単一の宣言的な定義にすることです。`config.py`という名前のファイルを作成し、以下のコードを貼り付けます。

    ```python theme={null}
    # Maps a logical role key to the actual Box group name.
    # Edit the names to match the groups in your enterprise.
    GOVERNANCE_GROUPS = {
        "IT": "IT Onboarding",
        "HR": "HR Onboarding",
        "All Employees": "All Employees",
    }

    # The standardized folder tree created for every new hire.
    # `collaborators` grants a governance group access at that exact level.
    # Because collaborations cascade, access granted on a folder also
    # applies to everything nested inside it.
    WORKSPACE_TEMPLATE = {
        "name": "Onboarding",
        "collaborators": [],
        "children": [
            {"name": "Personal", "collaborators": [], "children": []},
            {
                "name": "IT Setup",
                "collaborators": [{"group": "IT", "role": "editor"}],
                "children": [],
            },
            {
                "name": "HR Documents",
                "collaborators": [{"group": "HR", "role": "editor"}],
                "children": [],
            },
            {
                "name": "Policies & Templates",
                "collaborators": [{"group": "All Employees", "role": "viewer"}],
                "children": [],
            },
            {"name": "Team Resources", "collaborators": [], "children": []},
        ],
    }
    ```

    <Tip>
      構造とアクセスルールを1つのテンプレートに含めることで、*一貫性のある*、*管理された*オンボーディングを実現できます。将来のすべての採用者の設定方法を変更するには、プロビジョニングロジックではなく、このファイルを編集します。
    </Tip>
  </Step>

  <Step title="ガバナンスグループを解決する">
    フォルダをグループと共有する際には、そのグループのグループIDを調べる必要があります。`groups.py`という名前のファイルを作成し、以下のコードを貼り付けてください。これにより、ガバナンスグループが存在しない場合は作成され、存在する場合は既存のグループが検索されます。

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

    from config import GOVERNANCE_GROUPS

    def get_or_create_group(client: BoxClient, name: str) -> str:
        try:
            group = client.groups.create_group(
                name,
                description="Governance group managed by onboarding automation.",
            )
            print(f"Created group '{group.name}' ({group.id})")
            return group.id
        except BoxAPIError as error:
            if error.response_info.status_code != 409:
                raise
            for group in client.groups.get_groups(filter_term=name).entries:
                if group.name == name:
                    print(f"Using existing group '{group.name}' ({group.id})")
                    return group.id
            raise

    def resolve_governance_groups(client: BoxClient) -> dict:
        """Return a map of logical group key -> Box group ID."""
        return {
            key: get_or_create_group(client, name)
            for key, name in GOVERNANCE_GROUPS.items()
        }
    ```

    グループ名は企業内で一意である必要があります。そのため、指定したグループ名がすでに使われている場合、Boxは`409 Conflict`を返します。このステータスをキャッチして検索にフォールバックすることにより、この機能を安全な方法で繰り返し実行できます。
  </Step>

  <Step title="ワークスペースを作成してコラボレーションを適用する">
    `workspace.py`という名前のファイルを作成し、以下のコードを貼り付けます。これにより、テンプレート内の各フォルダがすべてのレベルで再帰的に作成され、そのフォルダがノードに定義されているグループに共有されます。

    ```python theme={null}
    from box_sdk_gen import (
        BoxClient,
        BoxAPIError,
        CreateFolderParent,
        CreateCollaborationItem,
        CreateCollaborationItemTypeField,
        CreateCollaborationAccessibleBy,
        CreateCollaborationAccessibleByTypeField,
        CreateCollaborationRole,
    )

    ROLE_MAP = {
        "editor": CreateCollaborationRole.EDITOR,
        "viewer": CreateCollaborationRole.VIEWER,
        "uploader": CreateCollaborationRole.UPLOADER,
        "co-owner": CreateCollaborationRole.CO_OWNER,
    }

    def is_already_collaborator(error: BoxAPIError) -> bool:
        # Box reports an existing collaborator differently for groups and users:
        # a group returns 409 with code "conflict", while a user can return
        # "user_already_collaborator". Match on both so re-runs are safe.
        body = error.response_info.body
        message = body.get("message", "").lower()
        return (
            body.get("code") == "user_already_collaborator"
            or "already a collaborator" in message
        )

    def create_folder(client: BoxClient, name: str, parent_id: str) -> str:
        try:
            folder = client.folders.create_folder(
                name, CreateFolderParent(id=parent_id)
            )
            return folder.id
        except BoxAPIError as error:
            # 409 means a folder with this name already exists under the parent.
            # Reuse it so re-running the service never creates duplicates.
            if error.response_info.status_code != 409:
                raise
            conflicts = error.response_info.body.get("context_info", {}).get(
                "conflicts", []
            )
            return conflicts[0]["id"]

    def add_group_collaboration(
        client: BoxClient, folder_id: str, group_id: str, role: str
    ):
        try:
            client.user_collaborations.create_collaboration(
                item=CreateCollaborationItem(
                    type=CreateCollaborationItemTypeField.FOLDER, id=folder_id
                ),
                accessible_by=CreateCollaborationAccessibleBy(
                    type=CreateCollaborationAccessibleByTypeField.GROUP, id=group_id
                ),
                role=ROLE_MAP[role],
            )
        except BoxAPIError as error:
            # Ignore if the group is already a collaborator on this folder.
            if not is_already_collaborator(error):
                raise

    def build_workspace(
        client: BoxClient, node: dict, parent_id: str, group_ids: dict
    ) -> str:
        folder_id = create_folder(client, node["name"], parent_id)

        for collaborator in node.get("collaborators", []):
            group_id = group_ids[collaborator["group"]]
            add_group_collaboration(
                client, folder_id, group_id, collaborator["role"]
            )
            print(
                f"  Shared '{node['name']}' with "
                f"{collaborator['group']} ({collaborator['role']})"
            )

        for child in node.get("children", []):
            build_workspace(client, child, folder_id, group_ids)

        return folder_id
    ```

    `build_workspace`はテンプレートを再帰的に処理するため、同じコードでフラットな構造や深くネストされた構造にも対応できます。ツリーは`config.py`でテンプレートを編集するだけで変更できます。
  </Step>

  <Step title="オンボーディングを調整する">
    `onboarding.py`という名前のファイルを作成し、以下のコードを貼り付けます。これにより、各要素が関連付けられます。グループが解決され、特定の採用者向けにワークスペースが作成され、必要に応じて新規採用者が各自のワークスペースに招待されます。

    ```python theme={null}
    import os
    from dotenv import load_dotenv
    from box_sdk_gen import (
        BoxClient,
        BoxAPIError,
        CreateCollaborationItem,
        CreateCollaborationItemTypeField,
        CreateCollaborationAccessibleBy,
        CreateCollaborationAccessibleByTypeField,
        CreateCollaborationRole,
    )

    from box_client import get_box_client
    from config import WORKSPACE_TEMPLATE
    from groups import resolve_governance_groups
    from workspace import build_workspace, is_already_collaborator

    load_dotenv()

    def invite_new_hire(client: BoxClient, folder_id: str, login: str):
        """Give the new hire access to their workspace root.

        Co-owner is used when the service account owns the folder. Otherwise we
        fall back to editor, because only owners and co-owners can grant co-owner.
        """
        me = client.users.get_user_me()
        folder = client.folders.get_folder_by_id(folder_id, fields=["owned_by"])
        if folder.owned_by and folder.owned_by.id == me.id:
            role = CreateCollaborationRole.CO_OWNER
        else:
            role = CreateCollaborationRole.EDITOR

        try:
            client.user_collaborations.create_collaboration(
                item=CreateCollaborationItem(
                    type=CreateCollaborationItemTypeField.FOLDER, id=folder_id
                ),
                accessible_by=CreateCollaborationAccessibleBy(
                    type=CreateCollaborationAccessibleByTypeField.USER, login=login
                ),
                role=role,
            )
            print(f"Invited {login} to workspace {folder_id} as {role.value}")
        except BoxAPIError as error:
            if not is_already_collaborator(error):
                raise

    def onboard_new_hire(hire: dict) -> str:
        client = get_box_client()

        group_ids = resolve_governance_groups(client)

        template = dict(WORKSPACE_TEMPLATE)
        template["name"] = f"{hire['name']} - Onboarding"

        parent_id = os.getenv("ONBOARDING_ROOT_FOLDER_ID", "0")
        workspace_id = build_workspace(client, template, parent_id, group_ids)

        if hire.get("email"):
            invite_new_hire(client, workspace_id, hire["email"])

        print(f"Workspace ready for {hire['name']} (ID: {workspace_id})")
        return workspace_id

    if __name__ == "__main__":
        # Simulate a hire event for a quick local test.
        onboard_new_hire({"name": "Jordan Rivera", "email": None})
    ```

    ワークスペースのルートに新規採用者を招待すると、新規採用者がそのルート内のすべてのフォルダを表示できる一方、他のユーザーのアクセス権限はサブフォルダ上でのグループコラボレーションによって引き続き管理されます。`invite_new_hire`は、サービスアカウントがワークスペースフォルダを所有している場合は**共同所有者**を付与し、所有していない場合は**編集者**にフォールバックします。これは、所有者または共同所有者のみが共同所有者のロールを付与できるためです。別のユーザーによって所有されるフォルダにある`ONBOARDING_ROOT_FOLDER_ID`を指定した場合、そのワークスペースフォルダはそのユーザーが所有しているため、フォールバックによって`403`が回避されます。誰も招待していない段階で構造をプロビジョニングするには、`email`を`None`のままにします。これは、新規採用者の業務開始日にまだBoxアカウントがない場合に便利です。
  </Step>

  <Step title="採用者イベントリスナーを作成する">
    `app.py`という名前のファイルを作成し、以下のコードを貼り付けます。このFlaskアプリケーションは、HRシステムから`hired`イベントを受け取り、イベントごとにワークスペースをプロビジョニングします。

    ```python theme={null}
    from dotenv import load_dotenv
    from flask import Flask, request, jsonify

    from onboarding import onboard_new_hire

    load_dotenv()
    app = Flask(__name__)

    @app.route("/hire", methods=["POST"])
    def handle_hire_event():
        event = request.get_json()

        if event.get("event") != "employee.hired":
            return jsonify({"status": "ignored"}), 200

        employee = event["employee"]
        hire = {
            "name": employee["full_name"],
            "email": employee.get("work_email"),
        }

        workspace_id = onboard_new_hire(hire)

        return jsonify({"status": "provisioned", "workspace_id": workspace_id}), 200

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

    <Note>
      実稼働環境では、通常は各リクエストの署名または共有シークレットを検証することにより、受信するリクエストが本当にHRシステムから送信されたものであることを確認する必要があります。未認証の公開エンドポイントからリソースをプロビジョニングすることは絶対に避けてください。
    </Note>

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

    ```
    onboarding-workspace/
    ├── .env
    ├── .venv/
    ├── app.py
    ├── box_client.py
    ├── config.py
    ├── groups.py
    ├── onboarding.py
    └── workspace.py
    ```
  </Step>

  <Step title="統合のテスト">
    実際のHRシステムに接続しなくても、プロビジョニングフロー全体をローカルでテストできます。この手順では、新規採用者が追加されたときにHRシステムから送信されるイベントをシミュレートします。

    <Note>
      この手順では、**2つのターミナルウィンドウ**を同時に開く必要があります。ターミナル1で、Flaskサーバーを実行します (これは常時稼働させておく必要があります)。ターミナル2では、このサーバーにテストリクエストを送信します。
    </Note>

    **ターミナル1 - サーバーを起動する**

    `onboarding-workspace`ディレクトリを開いていることと、仮想環境がアクティブ化されていることを確認します。

    ```bash theme={null}
    cd ~/onboarding-workspace
    source .venv/bin/activate
    python3 app.py
    ```

    次のように表示されます。

    ```
    * Running on http://127.0.0.1:5000
    ```

    このターミナルは起動したままにしておきます。

    **ターミナル2 - テスト用の採用者イベントを送信する:**

    新しいターミナルタブまたはウィンドウを開きます。curlを使用して、シミュレートされたHRイベントを送信します。メールアドレスを有効なアドレスに置き換えて招待をテストするか、`work_email`を削除してテストをスキップします。

    ```bash theme={null}
    curl -X POST http://127.0.0.1:5000/hire \
      -H "Content-Type: application/json" \
      -d '{
        "event": "employee.hired",
        "employee": {
          "full_name": "Jordan Rivera",
          "work_email": "jordan.rivera@example.com"
        }
      }'
    ```

    **結果の確認**

    ターミナル1に切り替えます。グループ、フォルダ、コラボレーションが作成されたことを示す出力が表示されます。

    ```
    Created group 'IT Onboarding' (12345678)
    Created group 'HR Onboarding' (12345679)
    Created group 'All Employees' (12345680)
      Shared 'IT Setup' with IT (editor)
      Shared 'HR Documents' with HR (editor)
      Shared 'Policies & Templates' with All Employees (viewer)
    Invited jordan.rivera@example.com to workspace 98765432
    Workspace ready for Jordan Rivera (ID: 98765432)
    ```

    **Boxで次のことを確認します。**

    ワークスペースの所有者が、自分の個人用または管理者ログインではなく、アプリの<Link href="/platform/user-types/#service-account">サービスアカウント</Link>であること。フォルダはサービスアカウントの独自のルート上に作成されるため、自分がコラボレータとして追加されていない限り、Boxウェブアプリの \[**すべてのファイル**] には表示**されません**。これは正常な動作です。以下のいずれかの方法で結果を確認します。

    * **サービスアカウントとしてAPIのクエリを実行する (推奨)。**この方法では、ワークスペースを作成したときと同じIDを使用します。ワークスペースのコンテンツを一覧表示し、各サブフォルダとそのコラボレータを確認します。

    ```python theme={null}
    from box_client import get_box_client

    client = get_box_client()

    # Replace with the workspace_id returned by the service.
    workspace_id = "98765432"

    for folder in client.folders.get_folder_items(workspace_id).entries:
        print(folder.type, folder.id, folder.name)
        collaborations = client.list_collaborations.get_folder_collaborations(folder.id)
        for collaboration in collaborations.entries:
            print(f"  {collaboration.accessible_by.name} - {collaboration.role.value}")
    ```

    * **Boxウェブアプリでコンテンツを開く。**自分がコラボレータになっているフォルダのみが表示されます。新規採用者として自分のメールアドレスを渡している場合は、[Boxウェブアプリ](https://app.box.com)を開き、`Jordan Rivera - Onboarding`を見つけて (共有フォルダとして表示される)、サブフォルダを開き、\[**共有**] をクリックして、コラボレータのリストを確認します。誰も招待されていない場合、フォルダを表示できるのはサービスアカウントのみです。

    * \*\*コンテンツマネージャでプレビューする。\*\***管理コンソール** > \[**コンテンツ**] > \[**コンテンツマネージャ**] の順に移動し、適切なユーザーを選択します。共有フォルダを表示できます。

    <Tip>
      ワークスペースがサービスアカウントのルートではなく、管理者が表示できる実際の場所に表示されるようにするには、管理者が所有する専用の**New Hires**フォルダを作成し、サービスアカウントをその共同所有者コラボレータとして追加して、`ONBOARDING_ROOT_FOLDER_ID`をそのフォルダのIDに設定します。そうすると、新しいワークスペースの所有者がその管理者になり、その管理者の \[**すべてのファイル**] に表示されるようになります。
    </Tip>

    **次の手順で再度実行します。**

    同じcurlリクエストを再度送信します。エラーは発生せず、重複するフォルダ、グループ、コラボレーションも作成されずにサービスが完了します。この冪等性により、実稼働環境で失敗または再発生したイベントを安全に再試行できます。
  </Step>
</Steps>

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

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

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

    * `BOX_CLIENT_ID`および`BOX_CLIENT_SECRET`が、開発者コンソール > \[構成] の値と一致していることを確認します。
    * `BOX_ENTERPRISE_ID`が、自分のEnterprise IDであることを確認します (開発者コンソール > \[一般設定] または管理コンソール > \[アカウントと請求] で確認できます)。
    * アプリが開発者コンソールで承認されていて、クライアント資格情報許可を使用していることを確認します。
  </Accordion>

  <Accordion title="403 Forbiddenエラーまたは「access_denied_insufficient_permissions」">
    グループ、ユーザー、コンテンツを管理するために必要なスコープやアクセスレベルがアプリに設定されていない:

    * 開発者コンソール > \[構成] で、\[**アプリアクセスレベル**] を \[**アプリ + Enterpriseアクセス**] に設定します。デフォルトの \[**アプリアクセスのみ**] では、Enterpriseユーザーおよびグループを作成することも管理することもできず、`403`が返されます。
    * 開発者コンソール > \[構成] > \[アプリケーションスコープ] で、\[**すべてのファイルとフォルダの読み取りと書き込み**]、\[**グループを管理する**]、\[**ユーザーを管理する**] を有効にします。
    * アクセスレベルやスコープを変更した後は、管理者が管理コンソールの \[**統合 > Platformアプリ**] でアプリを再承認する必要があります。アプリが再承認されるまで、これらの変更は反映されません。
  </Accordion>

  <Accordion title="ユーザーやグループが作成されず、作成時に403が返される">
    Enterpriseユーザーおよびグループの作成には、管理権限*と*Enterpriseアクセス権限が必要です。アプリの \[**アプリアクセスレベル**] が \[**アプリ + Enterpriseアクセス**] に設定されていること (デフォルトの \[**アプリアクセスのみ**] では、これらの操作に対して`403`が返される)、\[**グループを管理する**] および \[**ユーザーを管理する**] スコープが有効になっていること、Enterprise管理者がそれらの変更後にアプリを再承認したことを確認します。
  </Accordion>

  <Accordion title="フォルダまたはコラボレーションがすでに存在する (409競合)">
    これは再実行時に想定される動作であり、自動で処理されます。`create_folder`が競合レスポンスで返される既存のフォルダを再利用し、`is_already_collaborator`が既存のコラボレータを検出して、それをコラボレーションヘルパーがスキップします。これをBoxがどのように報告するかは、それがグループであるか (`409`とコード`conflict`、およびメッセージ「Group is already a collaborator (グループはすでにコラボレータになっています)」が表示) ユーザーであるか (`user_already_collaborator`) によって異なるため、ヘルパーは両方をチェックします。未処理の409が発生した場合は、`try`/`except`ブロックと`is_already_collaborator`ヘルパーを表示と同じように正確にコピーしたかどうかを確認してください。
  </Accordion>

  <Accordion title="新規採用者の招待が失敗するか保留中のままになる">
    メールアドレスが既存のBoxユーザーのものではない場合、招待が承認されるまでコラボレーションは`pending`状態で作成されます。このフローの一部として、新規採用者向けに管理対象アカウントをプロビジョニングするには、<Link href="/guides/users/create-managed-user">管理対象ユーザーを作成する</Link>を参照してください。
  </Accordion>
</AccordionGroup>

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

<AccordionGroup>
  <Accordion title="新規採用者をEnterpriseグループに追加する">
    グループとのフォルダの共有は、ガバナンスモデルの半分にすぎません。新規採用者をグループ*内に*追加すれば、そのグループがBox全体にわたってすでに保持しているすべてのコラボレーションを自動的に継承させることもできます。次のようにMemberships APIを使用します。

    ```python theme={null}
    from box_sdk_gen import (
        BoxClient,
        CreateGroupMembershipUser,
        CreateGroupMembershipGroup,
    )

    def add_user_to_group(client: BoxClient, user_id: str, group_id: str):
        client.memberships.create_group_membership(
            CreateGroupMembershipUser(id=user_id),
            CreateGroupMembershipGroup(id=group_id),
        )
    ```

    一般的には、すべての新規採用者を`All Employees`グループに追加し、初日から組織全体の共有フォルダにアクセスできるようにします。<Link href="/guides/collaborations/groups">グループとの共有</Link>を参照してください。

    <Note>
      すでにメンバーになっているユーザーを追加すると、`409` `BoxAPIError`が返されます。この手順の再実行の安全性を、サービスの他の部分と同じように維持するには、`workspace.py`で使用したものと同じ`try`/`except`パターンで呼び出しをラップし、競合を無視します。
    </Note>
  </Accordion>

  <Accordion title="新規採用者にオンボーディングHubへのアクセス権限を付与する">
    フォルダに加え、各新規採用者に<Link href="/guides/hubs-api">Box Hub</Link>へのアクセス権限も付与できます。Box Hubは、オンボーディング資料、ポリシー、FAQの厳選されたコンテンツポータルであり、Box AIと統合されているため、新規採用者はそのコンテンツ全体を対象として自然言語で質問できます。一般的には、1つの共有された「New Hire」Hubを用意し、プロビジョニングの際に各新規採用者 (または`All Employees`グループ) をコラボレータとして追加します。

    以下のヘルパーを`workspace.py`に追加します。フォルダとは異なり、コラボレータがすでに存在する場合、Hubは (`is_already_collaborator`に一致する`already a collaborator`の本文ではなく) 汎用の`409 Conflict`を返すため、ヘルパーはすべての`409`を冪等性スキップとして扱い、再実行の安全性を維持します。

    ```python theme={null}
    from box_sdk_gen import (
        CreateHubCollaborationV2025R0Hub,
        CreateHubCollaborationV2025R0AccessibleBy,
    )

    def add_hub_collaborator(
        client: BoxClient, hub_id: str, login: str, role: str = "viewer"
    ):
        try:
            client.hub_collaborations.create_hub_collaboration_v2025_r0(
                CreateHubCollaborationV2025R0Hub(id=hub_id),
                CreateHubCollaborationV2025R0AccessibleBy(type="user", login=login),
                role,
            )
        except BoxAPIError as error:
            # A hub returns a generic 409 "conflict" (not the folder-style
            # "already a collaborator" body) when the collaborator already
            # exists, so treat any 409 here as the idempotent skip case.
            if error.response_info.status_code != 409:
                raise
    ```

    HubのIDを`.env`に`ONBOARDING_HUB_ID`として格納し、新規採用者がメールアドレスを持っている場合はヘルパーを`onboard_new_hire`から呼び出します。

    ```python theme={null}
    hub_id = os.getenv("ONBOARDING_HUB_ID")
    if hub_id and hire.get("email"):
        add_hub_collaborator(client, hub_id, hire["email"])
    ```

    そうではなく、グループレベルでアクセス権限を付与するには、`CreateHubCollaborationV2025R0AccessibleBy(type="group", id=group_id)`を渡します。有効なロールは`viewer`、`editor`、`co-owner`です。

    <Note>
      これらの呼び出しを成功させるには、Box Hubsを<Link href="https://support.box.com/hc/en-us/articles/25822332454547-Configuring-Box-Hubs">自社に対して有効化</Link>しておく必要があります。`*_v2025_r0` SDKのメソッドは、Hubsエンドポイントに必要な`2025.0` APIバージョンを対象としています。

      サービスを実行しているID、つまりクライアント資格情報許可を使用するときのサービスアカウントでコラボレーションを管理するには、**Hubを所有または共同所有している**必要があります。そうでない場合、BoxはそのHubを認識できないため、`404 Not Found`をメッセージ`Authorization Failed`とともに返します。最も簡単なのは、Hubを所有できるようにサービスアカウントでHubを作成するか (\[<Link href="/guides/hubs-api/hubs/create-hub">Hubの作成</Link>] を使用)、サービスアカウントを既存Hubの共同所有者として追加する方法です。ロールと削除を管理するには、<Link href="/guides/hubs-api/hubs-collaborations/hub-collaborations">Hubコラボレーションの管理</Link>を参照してください。
    </Note>
  </Accordion>

  <Accordion title="ガバナンスポリシーをワークスペースに適用する">
    プロビジョニング済みのツリーに制御レイヤーを追加します。<Link href="/guides/metadata/index">メタデータ</Link>インスタンスをアタッチして各ワークスペースに新規採用者の部門と開始日のタグを付け、<Link href="/guides/retention-policies">リテンションポリシー</Link>を適用して、オンボーディングドキュメントがスケジュールに基づきアーカイブまたは削除されるようにします。また、メタデータを使用することで、企業全体のワークスペースが検索可能かつ報告可能になります。
  </Accordion>

  <Accordion title="イベントエンドポイントを保護する">
    `/hire`エンドポイントを権限ありとして扱います。HRシステムからのすべてのリクエストに共有シークレットまたは署名を要求し、内部へのトラフィックを既知のソースのみに制限して、サービスをHTTPSの背後で実行します。認証できないリクエストは、プロビジョニングコードに到達する前にすべて拒否されます。
  </Accordion>

  <Accordion title="プロビジョニングの回復性を確保する">
    HR統合は、イベントを複数回、順不同で配信できます。このチュートリアルの冪等性のヘルパーにより、すでに重複は防止されていますが、大量のイベントを扱うためには、イベントをキューに登録すること、一過性の障害をバックオフによって再試行すること、各プロビジョニングの実行をログに記録してオンボーディング対象者の身元と日時の監査証跡を保持することも必要になります。
  </Accordion>

  <Accordion title="オンボーディングとプロビジョニング解除を組み合わせる">
    ライフサイクルを完成させるには、オフボーディングのプロセスが必要です。HRシステムで退職イベントが発生した場合は、離職する従業員のコンテンツを転送し、その従業員のアクセス権限を削除します。<Link href="/guides/users/deprovision/index">ユーザーのプロビジョニング解除</Link>および<Link href="/guides/users/deprovision/transfer-folders">ファイルとフォルダの転送</Link>を参照してください。
  </Accordion>
</AccordionGroup>

## 次の手順

<CardGroup cols={2}>
  <Card title="ユーザーとコンテンツをプロビジョニングする" href={localizeLink("/guides/users/provision/index")} icon="users" arrow="true">
    ユーザー、グループ、共有フォルダ構造のプロビジョニングに関するBoxのエンドツーエンドのパターンを確認します。
  </Card>

  <Card title="Collaborations APIリファレンス" href={localizeLink("/reference/post-collaborations")} icon="code" arrow="true">
    コラボレーションの作成に関する詳細なAPI仕様を確認します。
  </Card>
</CardGroup>

<RelatedLinks
  title="関連するガイド"
  items={[
{ label: translate("Sharing with groups"), href: "/guides/collaborations/groups", badge: "GUIDE" },
{ label: translate("Setup shared folders"), href: "/guides/users/provision/shared-folders", badge: "GUIDE" },
{ label: translate("Create a managed user"), href: "/guides/users/create-managed-user", badge: "GUIDE" },
{ label: translate("Create a folder"), href: "/guides/folders/single/create", badge: "GUIDE" },
{ label: translate("Select an authentication method"), href: "/guides/authentication/select", badge: "GUIDE" }
]}
/>
