Build a sales RFP answer bank with Box Hubs and AI
Build an AI-powered RFP answer bank that indexes curated sales content in a Box Hub, embeds the portal in your CRM, and lets reps query past proposals using Box AI.
Use this file to discover all available pages before exploring further.
Responding to an RFP often means searching through scattered documents, Slack threads, and tribal knowledge to find the right answer. Reps can waste hours rewriting responses that already exist somewhere in the organization - or worse, they guess and introduce inaccurate information.This tutorial builds a self-service RFP answer bank powered by Box:
The sales team curates approved proposals and RFP responses.
Your application sets up a Sales Hub through the Hubs API to index that content, and embeds the hub portal in the CRM.
Sales reps query the hub with natural language using Box AI, getting accurate answers grounded in approved, permission-controlled content.
This solution combines three Box Platform capabilities:
Component
Purpose
API
Hubs
Index curated content into a searchable, permission-inheriting portal
POST /2.0/hubs
Hub items
Add folders and files to the hub
POST /2.0/hubs/:id/manage_items
Box AI
Answer natural language questions grounded in hub content
POST /2.0/ai/ask
Box Hubs inherit permissions from the underlying source files. Reps only see answers derived from content they already have access to in Box. No separate access control layer is required.
1
Organize your RFP content in Box
Before building the integration, organize your approved sales content. A clean folder structure improves both Box AI answer quality and content governance.Create a folder structure in Box similar to the following:
Note the folder IDs for the top-level Sales Content folder and each subfolder. You use these when adding items to the hub.
Ensure the authenticated user has access to this content. Since this tutorial uses CCG with a user_id, the client acts as that specific user. The user must have at least Viewer access to all folders you plan to add to the hub. If you are using a service account (enterprise_id instead of user_id), you must invite the service account email as a collaborator on the folders.To find your service account email: go to the Developer Console, open your app, and look under App Details for the Service Account ID (it looks like AutomationUser_xxxxx_xxxxxx@boxdevedition.com).
Keep the content curated and current. The quality of Box AI answers depends directly on the quality of the source material. Remove outdated proposals and archive superseded pricing guides.
2
Set up the development environment
Open your terminal and create a new project directory:
mkdir sales-rfp-hub && cd sales-rfp-hub
Create and activate a Python virtual environment:
python3 -m venv .venvsource .venv/bin/activate
After activation, your terminal prompt shows (.venv) at the beginning. This confirms you are working inside the virtual environment.
Every time you open a new terminal window or tab, you must re-activate the virtual environment by running 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:
pip install box-sdk-gen python-dotenv
Create a .env file to store your credentials then add the following content. Replace the placeholder values with your actual credentials from the Box Developer Console:
Never commit .env files to version control. Add .env to your .gitignore.
Understanding environment variables: The .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.
3
Authenticate the Box client
Create a new file called box_client.py in your project directory. Open the file and paste the following code:
This tutorial uses user_id (not enterprise_id) to authenticate as a specific Box user. This means the client acts with that user’s permissions and can access content the user has been invited to. Find your user ID in the Admin Console under Users, or by clicking your avatar in the Box web app and checking the URL.
Client Credentials Grant is recommended for server-to-server automations where no end user is present. Make sure Generate User Access Tokens is enabled in your app’s configuration. For other authentication options, see .
4
Provision the Sales Hub
Create a file called create_hub.py. This script creates a new hub and populates it with your sales content folders:
import osfrom dotenv import load_dotenvfrom box_sdk_gen import ( BoxClient, FolderReferenceV2025R0, HubItemOperationV2025R0, HubItemOperationV2025R0ActionField,)from box_client import get_box_clientload_dotenv()def create_sales_hub(client: BoxClient) -> str: hub = client.hubs.create_hub_v2025_r0( "Sales RFP Answer Bank", description=( "Curated library of approved proposals, RFP responses, " "competitive intelligence, and pricing materials for the sales team." ), ) print(f"Created hub: {hub.title} (ID: {hub.id})") return hub.iddef add_content_to_hub( client: BoxClient, hub_id: str, folder_id: str): client.hub_items.manage_hub_items_v2025_r0( hub_id, operations=[ HubItemOperationV2025R0( action=HubItemOperationV2025R0ActionField.ADD, item=FolderReferenceV2025R0(id=folder_id), ) ], ) print(f"Added folder {folder_id} to hub {hub_id}")def main(): client = get_box_client() hub_id = create_sales_hub(client) sales_folder_id = os.getenv("SALES_CONTENT_FOLDER_ID") add_content_to_hub(client, hub_id, sales_folder_id) print(f"\nSales Hub ready. Hub ID: {hub_id}") print("Share this hub with your sales team or embed it in your CRM.")if __name__ == "__main__": main()
Run the script:
python create_hub.py
You should see output confirming the hub was created and content was added. Save the hub ID printed in the output. You need it in the following steps.
Adding a top-level folder automatically indexes all files within it, including subfolders. When new files are added to those folders in Box, the hub content updates automatically.
5
Manage hub access
Control who can access the Sales Hub by adding collaborations. This ensures only the right people can query the content.Create a file called manage_access.py:
from dotenv import load_dotenvfrom box_sdk_gen import ( BoxClient, CreateHubCollaborationV2025R0Hub, CreateHubCollaborationV2025R0AccessibleBy,)from box_client import get_box_clientload_dotenv()def add_hub_collaborator( client: BoxClient, hub_id: str, user_email: str, role: str = "viewer",): collaboration = client.hub_collaborations.create_hub_collaboration_v2025_r0( CreateHubCollaborationV2025R0Hub(id=hub_id), CreateHubCollaborationV2025R0AccessibleBy( type="user", login=user_email ), role, ) print(f"Added {user_email} as {role} to hub {hub_id}") return collaborationdef main(): client = get_box_client() hub_id = "YOUR_HUB_ID" # Replace with the hub ID from Step 4 sales_reps = [ "rep1@example.com", "rep2@example.com", ] for email in sales_reps: add_hub_collaborator(client, hub_id, email, role="viewer")if __name__ == "__main__": main()
Use the viewer role for sales reps who only need to query content, and editor for sales managers who curate the library. For details on all available roles, see .
6
Query the hub with Box AI
The most powerful part of this integration is letting reps ask natural language questions against the curated content. Create a file called query_hub.py:
from dotenv import load_dotenvfrom box_sdk_gen import ( BoxClient, CreateAiAskMode, AiItemAsk,)from box_client import get_box_clientload_dotenv()def ask_sales_question( client: BoxClient, hub_id: str, question: str) -> str: response = client.ai.create_ai_ask( mode=CreateAiAskMode.SINGLE_ITEM_QA, prompt=question, items=[ AiItemAsk( type="hubs", id=hub_id, ) ], ) return response.answerdef main(): client = get_box_client() hub_id = "YOUR_HUB_ID" # Replace with the hub ID from Step 4 questions = [ "What is our standard SLA for enterprise support?", "How do we handle data residency requirements for EU customers?", "What security certifications do we hold?", "What discount tiers are available for 500+ seat deals?", ] for question in questions: print(f"\nQ: {question}") answer = ask_sales_question(client, hub_id, question) print(f"A: {answer}")if __name__ == "__main__": main()
Run:
python query_hub.py
Box AI searches the hub’s indexed content and returns answers grounded in your approved materials. Because hubs inherit Box permissions, the AI only references documents the querying user has access to.At this point, your project directory should contain the following files:
You should see your Box user’s name and email. If you see invalid_client, check your .env credentials.2. Create the hub and add content:
python create_hub.py
You should see:
Created hub: Sales RFP Answer Bank (ID: 12345678)Added folder 987654321 to hub 12345678Sales Hub ready. Hub ID: 12345678
Copy the hub ID from the output. Open query_hub.py and manage_access.py and replace YOUR_HUB_ID with this value.
After creating the hub and adding content, allow a few minutes for Box to index the files before running queries. Queries against a newly created hub may return empty or incomplete results until indexing completes.
3. Query the hub:
python query_hub.py
You should see questions printed followed by answers drawn from your sales content. If answers are empty or generic, verify that:
The hub ID is correct.
The content folder contains documents (not just subfolders).
Enough time has passed for indexing to complete.
4. Verify in Box:Open the Box web app and navigate to Hubs in the left sidebar. Your “Sales RFP Answer Bank” hub should appear with the content folders listed.
8
Keep the hub current
This step provides reference patterns for keeping your hub up to date. You don’t need to run these now. Integrate them into your application code when your sales content changes.
A stale answer bank is worse than no answer bank. Automate content freshness by updating hub items when your sales content changes.
from box_sdk_gen import ( BoxClient, FolderReferenceV2025R0, HubItemOperationV2025R0, HubItemOperationV2025R0ActionField,)def refresh_hub_content( client: BoxClient, hub_id: str, new_folder_id: str): """Add a new content folder to the hub.""" client.hub_items.manage_hub_items_v2025_r0( hub_id, operations=[ HubItemOperationV2025R0( action=HubItemOperationV2025R0ActionField.ADD, item=FolderReferenceV2025R0(id=new_folder_id), ) ], ) print(f"Added folder {new_folder_id} to hub {hub_id}")def remove_outdated_content( client: BoxClient, hub_id: str, old_folder_id: str): """Remove a folder from the hub.""" client.hub_items.manage_hub_items_v2025_r0( hub_id, operations=[ HubItemOperationV2025R0( action=HubItemOperationV2025R0ActionField.REMOVE, item=FolderReferenceV2025R0(id=old_folder_id), ) ], ) print(f"Removed folder {old_folder_id} from hub {hub_id}")
Pair this with a quarterly content review process. Sales reviews the hub contents, archives outdated materials, and adds new approved content. The hub reindexes automatically.
Your virtual environment is not activated. Run source .venv/bin/activate from the project directory before running any python commands. Each new terminal tab needs its own activation.
invalid_client: The client credentials are invalid
Check your .env file:
Verify BOX_CLIENT_ID and BOX_CLIENT_SECRET match the values in Developer Console > Platform Apps > App > Configuration.
Confirm BOX_USER_ID is a valid Box user ID (numeric, found in Admin Console > Users).
Ensure your app is authorized in the Developer Console.
Confirm the app type is Client Credentials Grant with Generate User Access Tokens enabled.
403 Forbidden or 'access_denied_insufficient_permissions'
The authenticated user does not have the required scopes or permissions:
Verify that Read all files and folders and Write all files and folders scopes are enabled on your app.
Confirm the Manage AI scope is enabled (required for Box AI queries).
If using a service account, ensure it has been invited as a collaborator on the content folders.
404 Not Found when creating the hub or adding items
The folder ID is incorrect or the authenticated user does not have access to it. Double-check:
The folder ID in your .env file matches the URL in Box (https://app.box.com/folder/123456789 means the folder ID is 123456789).
The user specified in BOX_USER_ID has at least Viewer access to the folder.
Box AI returns empty or irrelevant answers
This usually means the hub content has not finished indexing, or the content is not suitable for the question:
Wait a few minutes after adding content before querying. Indexing is not instantaneous.
Verify the hub contains files (not just empty folders) by checking in the Box web app under Hubs.
Ensure your documents contain text content (scanned image PDFs without OCR are not searchable).
Try asking a question that directly relates to content you know exists in the hub.
Hub not visible in Box web app
The hub may have been created successfully but the current user does not have collaboration access. Run manage_access.py to add yourself or check the hub owner. Alternatively, verify Box Hubs is enabled for your enterprise by checking with your Box administrator.
You can embed the hub’s AI chat interface directly into your CRM (such as Salesforce, HubSpot, or a custom internal tool), giving reps a focused chatbot experience powered by the hub’s content. Box supports two embed modes for hub AI chat: a chat button and a chat widget.For the full list of embed parameters and options, see .
Users accessing the embedded hub authenticate through their existing Box session. If they are already signed in to Box (common with SSO-enabled enterprises), the chat loads seamlessly with no additional login prompt. Users need at least Viewer permissions on the hub.
If you have separate sales teams (by region, vertical, or product line), you can provision a dedicated hub for each. First, update create_sales_hub in create_hub.py to accept an optional title parameter instead of the hardcoded value. Then loop over your teams:
teams = { "EMEA": "EMEA_FOLDER_ID", "APAC": "APAC_FOLDER_ID", "Americas": "AMERICAS_FOLDER_ID",}for team, folder_id in teams.items(): hub_id = create_sales_hub(client, title=f"Sales RFP Bank — {team}") add_content_to_hub(client, hub_id, folder_id)
Build a conversational RFP assistant
The POST /2.0/ai/ask endpoint accepts a dialogue_history parameter that carries previous Q&A pairs. Maintain a list of exchanges and pass it with each request so reps can ask follow-ups like “Can you elaborate on the data residency point?” without losing context.Add the following to query_hub.py, or create a new script with the same imports and client setup:
Use to monitor how often the hub is queried and which content is accessed most frequently. This data helps sales prioritize content curation and measure the ROI of the answer bank.
Enforce content governance
Combine hubs with to automatically archive or delete outdated sales materials. Use to tag content with review dates and ownership.