hired event, the service authenticates to Box, provisions a standardized personal folder tree for the new hire, grants the right groups access at the right levels, and optionally invites the new hire to their workspace. Every new hire automatically gets an identical, governed structure.
What you are building
By the end of this tutorial, you have a working Python service that:- Receives an HR
hiredevent through a webhook endpoint. - Resolves (or creates) the governance groups that control access, such as IT and HR.
- Builds a standardized personal folder tree for the new hire using the Folders API.
- Grants each governance group access at the correct folder level using the Collaboration API.
- Optionally invites the new hire to their workspace so they have access on day one.
- Runs idempotently, so replaying the same event never creates duplicate folders or collaborations.
Prerequisites
Before you start, make sure you have the following:- A Box account with administrator access. Any works, and a free also works (it authorizes Platform Apps automatically, so you can skip the Admin Console authorization step).
- A Box application configured with Client Credentials Grant authentication, authorized in the Admin Console.
- Python 3.11 or higher.
- The following scopes enabled on your app:
- Read and write all files and folders stored in Box
- Manage groups
- Manage users
- The app’s App Access Level set to App + Enterprise Access (on the Configuration tab). The default App Access Only limits the service account to its own content and cannot create or manage enterprise users and groups.
- Your Box enterprise ID (found in the Developer Console by clicking your profile icon, or in the Admin Console under Account & Billing).
enterprise_id, the app acts as the enterprise service account. This service account can only manage enterprise users and groups when the app’s App Access Level is set to App + Enterprise Access. After you enable the Manage groups and Manage users scopes and change the access level, an administrator must reauthorize the app in the Admin Console under Integrations > Platform Apps for those changes to take effect.Step-by-step process
This solution combines four Box Platform capabilities:Set up the development environment
- Open your terminal and create a new project directory:
- Create and activate a Python virtual environment:
(.venv) at the beginning. This confirms you are working inside the virtual environment.source .venv/bin/activate from the project directory. If you see ModuleNotFoundError when running commands, it usually means the venv is not activated.- Install the required packages:
- Create a
.envfile to store your credentials, then add the following content. Replace the placeholder values with your actual credentials from the Box Developer Console:
ONBOARDING_ROOT_FOLDER_ID is the folder where each new hire’s workspace is created. Leave it as 0 to create workspaces in the service account’s root, or set it to the ID of a dedicated New Hires folder..env file stores sensitive values (your actual credentials). Your Python code reads these values by referencing their names using os.getenv("VARIABLE_NAME"). For example, os.getenv("BOX_CLIENT_ID") looks up the value stored next to BOX_CLIENT_ID= in your .env file. When you copy the code in the following steps, keep the quoted variable names exactly as shown. Do not replace them with your actual credentials.Authenticate the Box client
box_client.py in your project directory and paste the following code:Define the workspace template
config.py and paste the following code:Resolve the governance groups
groups.py and paste the following code. It creates each governance group if it does not exist, and looks up the existing group if it does:409 Conflict if the group already exists. Catching that status and falling back to a lookup makes the function safe to run repeatedly.Build the workspace and apply collaborations
workspace.py and paste the following code. It recursively creates each folder in the template and, at every level, shares the folder with the groups defined for that node:build_workspace walks the template recursively, the same code handles a flat structure or a deeply nested one. To change the tree, you only edit the template in config.py.Orchestrate onboarding
onboarding.py and paste the following code. This ties the pieces together: it resolves the groups, builds the workspace for a specific hire, and optionally invites the new hire to their workspace root:invite_new_hire grants co-owner when the service account owns the workspace folder, and falls back to editor otherwise - because only an owner or co-owner can grant the co-owner role. If you point ONBOARDING_ROOT_FOLDER_ID at a folder owned by another user, the workspace folders are owned by that user, so the fallback prevents a 403. Leave email as None to provision the structure without inviting anyone yet - useful when the hire’s Box account does not exist on their start date.Create the hire event listener
app.py and paste the following code. This Flask application receives hired events from your HR system and provisions a workspace for each one:Test the integration
onboarding-workspace directory and the virtual environment is activated:work_email to skip it:- Query the API as the Service Account (recommended). This uses the same identity that created the workspace. List the workspace’s contents and confirm each subfolder and its collaborators:
-
Open it in the Box web app. You only see the folder here if you are a collaborator on it. If you passed your own email as the new hire, open the Box web app, find
Jordan Rivera - Onboarding(it appears as a shared folder), open a subfolder, click Sharing, and check the collaborator list. If no one was invited, the folder is visible only to the Service Account. - Preview it in Content Manager. Navigate to the Admin Console > Content > Content Manager, then select the relevant user. You are able to view the shared folder.
Troubleshooting
ModuleNotFoundError: No module named '...'
ModuleNotFoundError: No module named '...'
source .venv/bin/activate from the project directory before running any python3 commands. Each new terminal tab needs its own activation.invalid_client: The client credentials are invalid
invalid_client: The client credentials are invalid
.env file:- Verify
BOX_CLIENT_IDandBOX_CLIENT_SECRETmatch the values in Developer Console > Configuration. - Confirm
BOX_ENTERPRISE_IDis your enterprise ID (found in Developer Console > General Settings, or Admin Console > Account & Billing). - Ensure your app is authorized in the Developer Console and uses Client Credentials Grant.
403 Forbidden or 'access_denied_insufficient_permissions'
403 Forbidden or 'access_denied_insufficient_permissions'
- Set the App Access Level to App + Enterprise Access in Developer Console > Configuration. The default App Access Only cannot create or manage enterprise users and groups, which returns a
403. - Enable Read and write all files and folders, Manage groups, and Manage users in Developer Console > Configuration > Application Scopes.
- After changing the access level or scopes, an administrator must reauthorize the app in the Admin Console under Integrations > Platform Apps. These changes do not take effect until the app is reauthorized.
Users or groups are not created and creation returns 403
Users or groups are not created and creation returns 403
403 for these operations), that the Manage groups and Manage users scopes are enabled, and that an enterprise admin reauthorized the app after those changes.A folder or collaboration already exists (409 Conflict)
A folder or collaboration already exists (409 Conflict)
create_folder reuses the existing folder returned in the conflict response, and is_already_collaborator detects an existing collaborator so the collaboration helpers skip it. Note that Box reports this differently for groups (409 with code conflict and the message “Group is already a collaborator”) and users (user_already_collaborator), which is why the helper checks both. If you see an unhandled 409, confirm you copied the try/except blocks and the is_already_collaborator helper exactly as shown.The new hire invite fails or stays pending
The new hire invite fails or stays pending
pending state until they accept the invitation. To provision managed accounts for new hires as part of this flow, see .Scaling to production
Add the new hire to enterprise groups
Add the new hire to enterprise groups
All Employees group so org-wide shares reach them on day one. See .409 BoxAPIError. To keep this step safe to re-run like the rest of the service, wrap the call in the same try/except pattern used in workspace.py and ignore the conflict.Give the new hire access to an onboarding Hub
Give the new hire access to an onboarding Hub
All Employees group) as a collaborator during provisioning.Add the following helper to workspace.py. Unlike a folder, a hub returns a generic 409 Conflict (not the already a collaborator body that is_already_collaborator matches) when the collaborator already exists, so the helper treats any 409 as an idempotent skip - keeping it safe to re-run:.env as ONBOARDING_HUB_ID, then call the helper from onboard_new_hire when the hire has an email:CreateHubCollaborationV2025R0AccessibleBy(type="group", id=group_id). Valid roles are viewer, editor, and co-owner.*_v2025_r0 SDK methods target the 2025.0 API version, which the Hubs endpoints require.The identity running the service - the Service Account when you use Client Credentials Grant - must own or co-own the Hub to manage its collaborations. If it does not, Box returns 404 Not Found with the message Authorization Failed, because it cannot see the Hub. The simplest approach is to have the Service Account create the Hub (via ) so it owns it, or add the Service Account as a co-owner of an existing Hub. To manage roles and removals, see .Apply governance policies to the workspace
Apply governance policies to the workspace
Secure the event endpoint
Secure the event endpoint
/hire endpoint as privileged. Require a shared secret or signature on every request from your HR system, restrict inbound traffic to known sources, and run the service behind HTTPS. Reject any request you cannot authenticate before it reaches your provisioning code.Make provisioning resilient
Make provisioning resilient
Pair onboarding with deprovisioning
Pair onboarding with deprovisioning
