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

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

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">
          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">
          Already have an account? Log in
        </a>
      </div>
    </div>;
};

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

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:

1. The sales team curates approved proposals and RFP responses.
2. Your application sets up a Sales Hub through the Hubs API to index that content, and embeds the hub portal in the CRM.
3. Sales reps query the hub with natural language using Box AI, getting accurate answers grounded in approved, permission-controlled content.

## What you are building

By the end of this tutorial, you have a working integration that:

* Provisions a Sales Hub programmatically using the Box Hubs API.
* Populates the hub with curated folders containing approved RFP content.
* Embeds the hub's AI chat interface in your CRM.
* Queries the hub using Box AI to answer sales questions grounded in approved content.

## Prerequisites

Before you start, make sure you have the following:

* A <Link href="https://www.box.com/pricing">Box Enterprise account</Link> with **Box Hubs** enabled. See <Link href="https://support.box.com/hc/en-us/articles/25822332454547-Configuring-Box-Hubs">Configuring Box Hubs</Link>.
* A Box application configured with **Client Credentials Grant** authentication and **Generate User Access Tokens** enabled.
* Python 3.11 or higher.
* The following scopes enabled on your app:
  * Read all files and folders stored in Box
  * Write all files and folders stored in Box
  * Manage AI
* Your Box user ID (found in the Admin Console under **Users**).

## Step-by-step process

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`                |

<Info>
  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.
</Info>

<Steps>
  <Step title="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, or [copy this shared folder](https://previewteam.app.box.com/s/qq7uws38kuo3fl81fzmjjbgrkemprqlk) to your Box account. It contains sample files for each subfolder listed below.

    ```
    Sales Content/
    ├── RFP Responses/
    │   ├── 2025-Q1-Acme-RFP-Response.pdf
    │   ├── 2024-Q4-Enterprise-Security-RFP.docx
    │   └── 2024-Q3-Healthcare-Compliance-RFP.pdf
    ├── Proposals/
    │   ├── Enterprise-Platform-Proposal-Template.docx
    │   └── Mid-Market-Proposal-Template.docx
    ├── Competitive Intelligence/
    │   ├── Competitor-Comparison-Matrix.xlsx
    │   └── Win-Loss-Analysis-Q1-2025.pdf
    └── Pricing/
        ├── Enterprise-Pricing-Guide.pdf
        └── Volume-Discount-Schedule.xlsx
    ```

    Note the folder IDs for the top-level `Sales Content` folder and each subfolder. You use these when adding items to the hub.

    <Warning>
      **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](https://app.box.com/developers/console), open your app, and look under **App Details** for the **Service Account ID** (it looks like `AutomationUser_xxxxx_xxxxxx@boxdevedition.com`).
    </Warning>

    <Tip>
      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.
    </Tip>
  </Step>

  <Step title="Set up the development environment">
    1. Open your terminal and create a new project directory:

    ```bash theme={null}
    mkdir sales-rfp-hub && cd sales-rfp-hub
    ```

    2. Create and activate a Python virtual environment:

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

    After activation, your terminal prompt shows `(.venv)` at the beginning. This confirms you are working inside the virtual environment.

    <Note>
      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.
    </Note>

    3. Install the required packages:

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

    4. 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:

    ```bash theme={null}
    BOX_CLIENT_ID=your_client_id
    BOX_CLIENT_SECRET=your_client_secret
    BOX_USER_ID=your_box_user_id
    SALES_CONTENT_FOLDER_ID=your_sales_content_folder_id
    ```

    <Warning>
      Never commit `.env` files to version control. Add `.env` to your `.gitignore`.
    </Warning>

    <Note>
      **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.
    </Note>
  </Step>

  <Step title="Authenticate the Box client">
    Create a new file called `box_client.py` in your project directory. Open the file and paste the following code:

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

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

    <Note>
      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.
    </Note>

    <Tip>
      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 <Link href="/guides/authentication/select">Select an authentication method</Link>.
    </Tip>
  </Step>

  <Step title="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:

    ```python theme={null}
    import os
    from dotenv import load_dotenv
    from box_sdk_gen import (
        BoxClient,
        FolderReferenceV2025R0,
        HubItemOperationV2025R0,
        HubItemOperationV2025R0ActionField,
    )

    from box_client import get_box_client

    load_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.id

    def 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:

    ```bash theme={null}
    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.

    <Note>
      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.
    </Note>
  </Step>

  <Step title="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`:

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

    from box_client import get_box_client

    load_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 collaboration

    def 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()
    ```

    <Tip>
      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 <Link href="/guides/hubs-api/hubs-collaborations/hub-collaborations">Manage hub collaborations</Link>.
    </Tip>
  </Step>

  <Step title="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`:

    ```python theme={null}
    from dotenv import load_dotenv
    from box_sdk_gen import (
        BoxClient,
        CreateAiAskMode,
        AiItemAsk,
    )

    from box_client import get_box_client

    load_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.answer

    def 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:

    ```bash theme={null}
    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:

    ```
    sales-rfp-hub/
    ├── .env
    ├── .venv/
    ├── box_client.py
    ├── create_hub.py
    ├── manage_access.py
    └── query_hub.py
    ```
  </Step>

  <Step title="Test the integration">
    Before moving to production, verify each component works correctly.

    <Note>
      Make sure you are in the `sales-rfp-hub` directory and the virtual environment is activated before running any commands:

      ```bash theme={null}
      cd ~/sales-rfp-hub
      source .venv/bin/activate
      ```
    </Note>

    **1. Verify authentication:**

    Run a quick check to confirm your credentials are valid:

    ```bash theme={null}
    python -c "from box_client import get_box_client; client = get_box_client(); user = client.users.get_user_me(); print(f'Authenticated as: {user.name} ({user.login})')"
    ```

    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:**

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

    You should see:

    ```
    Created hub: Sales RFP Answer Bank (ID: 12345678)
    Added folder 987654321 to hub 12345678

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

    <Warning>
      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.
    </Warning>

    **3. Query the hub:**

    ```bash theme={null}
    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](https://app.box.com) and navigate to **Hubs** in the left sidebar. Your "Sales RFP Answer Bank" hub should appear with the content folders listed.
  </Step>

  <Step title="Keep the hub current">
    <Info>
      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.
    </Info>

    A stale answer bank is worse than no answer bank. Automate content freshness by updating hub items when your sales content changes.

    ```python theme={null}
    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}")
    ```

    <Tip>
      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.
    </Tip>
  </Step>
</Steps>

## Troubleshooting

<AccordionGroup>
  <Accordion title="ModuleNotFoundError: No module named '...'">
    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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>
</AccordionGroup>

## Embed the hub in your CRM

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 <Link href="/guides/embed/box-embed/#box-hubs-ai-chat-embedding">Box Hubs AI Chat embedding</Link>.

<Info>
  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.
</Info>

## Scaling to production

<AccordionGroup>
  <Accordion title="Automate hub provisioning for multiple teams">
    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:

    ```python theme={null}
    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)
    ```
  </Accordion>

  <Accordion title="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:

    ```python theme={null}
    from datetime import datetime, timezone
    from box_sdk_gen import AiDialogueHistory

    history = []

    while True:
        question = input("Q: ")
        response = client.ai.create_ai_ask(
            mode=CreateAiAskMode.SINGLE_ITEM_QA,
            prompt=question,
            items=[AiItemAsk(type="hubs", id=hub_id)],
            dialogue_history=history,
        )
        print(f"A: {response.answer}")
        history.append(
            AiDialogueHistory(
                prompt=question,
                answer=response.answer,
                created_at=datetime.now(timezone.utc).isoformat(),
            )
        )
    ```
  </Accordion>

  <Accordion title="Track usage and measure impact">
    Use <Link href="/guides/events/enterprise-events/for-enterprise">enterprise events</Link> 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.
  </Accordion>

  <Accordion title="Enforce content governance">
    Combine hubs with <Link href="/guides/retention-policies">retention policies</Link> to automatically archive or delete outdated sales materials. Use <Link href="/guides/metadata/index">metadata</Link> to tag content with review dates and ownership.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Invoice intake automation" href="/guides/tutorials/invoice-intake" icon="file-invoice" arrow="true">
    Automate accounts payable with Box AI Extract and metadata.
  </Card>

  <Card title="Box Hubs API reference" href="/guides/hubs-api/index" icon="server" arrow="true">
    See the full Hubs API guide for advanced hub management.
  </Card>
</CardGroup>

<RelatedLinks
  title="RELATED GUIDES"
  items={[
{ label: translate("Box Hubs overview"), href: "/guides/hubs-api/index", badge: "GUIDE" },
{ label: translate("Hubs API use cases"), href: "/guides/hubs-api/use-cases", badge: "GUIDE" },
{ label: translate("Manage hub items"), href: "/guides/hubs-api/hubs-items/hub-items", badge: "GUIDE" },
{ label: translate("Manage hub collaborations"), href: "/guides/hubs-api/hubs-collaborations/hub-collaborations", badge: "GUIDE" },
{ label: translate("Ask questions to Box AI"), href: "/guides/box-ai/ai-tutorials/ask-questions", badge: "GUIDE" }
]}
/>
