Skip to content
Beam Docs
Connectors

Box

Copy files out of Box, and into Box folders, from Studio or the TypeScript and Python SDKs.

Box can be a transfer source and a transfer destination. Box addresses items by ID, not by path: a file ID, or a folder ID, where 0 is the root folder. An item's ID is the number at the end of its Box URL (app.box.com/file/<id>, app.box.com/folder/<id>), and Studio's Box picker selects items for you.

Your Box access token stays with you, in Studio or in your SDK process. Workers never receive it:

  • reading from Box, a worker gets a short-lived download link for one version of one file;
  • writing to Box, a worker gets a token that can only upload to one file, and BeamCore commits the file with a separate token of the same kind.

Every token minted for a file is revoked once the file is committed or abandoned.

In Studio

Box is available in Studio v1.0.23 and later. Box transfers run in the Beam Transfer action, @beam/transfer 1.8.0 or later. A new Studio installs the latest version; an existing one keeps the version it has until you update it on the Registry page.

Set up the Box app

Box accepts only a redirect URI registered in its app, and every Studio has its own address, so each Studio connects through a Box app of its own. The organization that owns the Studio sets it up once, in Settings → Access → Box app:

  1. In the Box Developer Console, create a Platform App with User Authentication (OAuth 2.0).
  2. Under Configuration, add the redirect URI Studio shows, <your Studio's public URL>/connections/box/callback. Box accepts plain HTTP only for localhost.
  3. Under Application Scopes, select Read all files and folders stored in Box, Write all files and folders stored in Box and Manage webhooks, then save the app.
  4. Copy the app's client ID and client secret into Studio and select Save Box app.

A company Box account may need its Box admin to enable the app in the Box Admin Console. An account that signs in with SSO needs nothing more: Box sends the user through their identity provider while they connect.

Connect a Box account

Open Credentials → New credential → Object storage → Box and sign in to Box. Studio keeps the connection's tokens and refreshes them itself; they never leave Studio. Connecting the same Box account again reauthorizes the existing connection. If Box revokes access, Credentials offers Reconnect.

Use Box in a workflow

In a source or destination endpoint, choose your Box connection under Credential. The endpoint becomes a Box endpoint and opens the Box picker.

  • As a source, pick a file or a folder. A folder copies every file in it, subfolders included, each at its path inside the folder.
  • As a destination, pick a folder. Each file keeps the last part of its source name, and a file that already has that name in the folder gets a new version. A Box destination must be the only destination of its Beam Transfer step. Sources that would land under the same name are refused before anything is written.

Other storage endpoints name one object each. To copy several objects from a bucket, add one source endpoint per object; only a Box source can be a folder.

The Beam Transfer step starts one Beam transfer per file, two at a time, and the run page links each one in the Console. The step's Beam credential and the workflow's Billing API key must be the same key; see Connect Studio to your organization for the permissions it needs.

From an AI agent

Studio's MCP server has two Box tools, which use the Box connections already in Studio. The agent never handles a Box token.

  • beam.list_box_folder lists one page of a Box folder: its subfolders, and its files with their ID, size and SHA-1.
  • beam.start_box_transfer creates and runs a Box transfer workflow from up to 20 Box file IDs or one folder ID. The destination is a bucket (credentialId, bucket, objectKey) or a Box folder (credentialId, boxFolderId).

With the SDKs

The TypeScript SDK (@beam-network/sdk 0.11.0 or later) and the Python SDK (beam-network-sdk 0.15.0 or later) support Box. You bring a Box access token from your own Box app's OAuth flow.

Box as a source

import { BeamClient, R2ProviderConfig, boxFolderSources } from "@beam-network/sdk";

const beam = new BeamClient({ apiKey: process.env.BEAM_API_KEY! });

// Every file under a folder. For one file, pass
// [BoxProviderConfig.create({ file_id: "1234567890", token })] instead.
const sources = await boxFolderSources({ folder_id: "9876543210", token: process.env.BOX_ACCESS_TOKEN! });

const transfer = await beam.createTransfer({
  sources,
  destinations: [
    R2ProviderConfig.create({
      bucket: "archive",
      key: "box-export/",
      account_id: process.env.R2_ACCOUNT_ID!,
      access_key_id: process.env.R2_ACCESS_KEY_ID!,
      secret_access_key: process.env.R2_SECRET_ACCESS_KEY!,
    }),
  ],
  name: "box-export",
});
import asyncio
import os

from beam_network_sdk import BeamSDK, box_folder_sources
from beam_network_sdk.models import R2ProviderDestination


async def main() -> None:
    # Every file under a folder. For one file, pass
    # [BoxProviderSource(file_id="1234567890", token=token)] instead.
    # The listing makes blocking HTTP calls, so it runs in a thread.
    sources = await asyncio.to_thread(
        box_folder_sources, folder_id="9876543210", token=os.environ["BOX_ACCESS_TOKEN"]
    )
    async with BeamSDK(api_key=os.environ["BEAM_API_KEY"]) as beam:
        transfer = await beam.transfers.create_transfer(
            sources=sources,
            destinations=[
                R2ProviderDestination(
                    bucket="archive",
                    key="box-export/",
                    account_id=os.environ["R2_ACCOUNT_ID"],
                    access_key_id=os.environ["R2_ACCESS_KEY_ID"],
                    secret_access_key=os.environ["R2_SECRET_ACCESS_KEY"],
                )
            ],
        )
        await beam.transfers.wait_complete(transfer.transfer_id)


asyncio.run(main())
  • Each file is read at the version that is current when the transfer is prepared.
  • boxFolderSources [box_folder_sources] recurses into subfolders and keeps each file's path, so a destination key ending in / receives the folder's layout. It skips zero-byte files.
  • The SDK never refreshes your token, so the token must stay valid while the transfer runs. Box download links last about 15 minutes, and the SDK re-signs them with your token during a longer transfer.

Box as a destination

import { BeamClient, BoxDestinationConfig, BoxProviderConfig } from "@beam-network/sdk";

const beam = new BeamClient({ apiKey: process.env.BEAM_API_KEY! });

const transfer = await beam.createTransfer({
  sources: [BoxProviderConfig.create({ file_id: "1234567890", token: process.env.BOX_ACCESS_TOKEN! })],
  destinations: [
    BoxDestinationConfig.create({
      folder_id: "9876543210",
      // Your app's current Box access token, refreshed by your OAuth code.
      token_provider: async () => getBoxAccessToken(),
      client_id: process.env.BOX_CLIENT_ID!,
      client_secret: process.env.BOX_CLIENT_SECRET!,
    }),
  ],
  name: "box-to-box",
});

// Keep the client open until the transfer is terminal.
await beam.waitForTransfer(transfer.transfer_id);
await beam.close();
import asyncio
import os

from beam_network_sdk import BeamSDK, BoxProviderDestination, BoxProviderSource


async def main() -> None:
    async with BeamSDK(api_key=os.environ["BEAM_API_KEY"]) as beam:
        transfer = await beam.transfers.create_transfer(
            sources=[BoxProviderSource(file_id="1234567890", token=os.environ["BOX_ACCESS_TOKEN"])],
            destinations=[
                BoxProviderDestination(
                    folder_id="9876543210",
                    # Your app's current Box access token, sync or async.
                    token_provider=get_box_access_token,
                    client_id=os.environ["BOX_CLIENT_ID"],
                    client_secret=os.environ["BOX_CLIENT_SECRET"],
                )
            ],
        )
        # Keep the SDK open until the transfer is terminal.
        await beam.transfers.wait_complete(transfer.transfer_id)


asyncio.run(main())
  • Keep the client open until the transfer is terminal: BeamCore asks it to renew, revoke, finalize and abandon each upload. Closing it abandons the transfer, and no other client can resume it.
  • Tokens. token_provider returns your current Box access token for every Box call that needs it; refreshing it is your code's job. The SDK mints the workers' upload tokens from it and revokes them with your app's client_id and client_secret, or through your own revoke_token.
  • Names. A file of the same name in the folder gets a new version. Otherwise the SDK first creates an empty placeholder file, which it deletes if the transfer fails. file_name names the file when the transfer has one source.
  • One kind of upload per transfer. Files under 20 MiB and files of 20 MiB or more go in separate transfers, and so do large files that Box gives different part sizes. The SDK refuses a mix with BoxDestinationError code box_mixed_upload_kinds or box_part_size_mismatch.
  • Alone. A Box destination is its transfer's only destination.

Large files need their SHA-1

Box commits a file of 20 MiB or more only when it is given the SHA-1 of the whole file. A SHA-1 is a 40-character fingerprint of the file, not the file itself. When the source already stores one, your SDK or Studio reads it from the source's metadata and passes only that value to BeamCore:

  • a Box file always has one;
  • an S3 or R2 object has one when it was stored with a full-object SHA-1 checksum, for example uploaded in a single request with aws s3api put-object --checksum-algorithm SHA1. The combined checksum of a multipart upload does not count.

Otherwise BeamCore has the SHA-1 computed before it commits the file. Independent operators, other than the ones uploading the file, each read the whole source once more over HTTPS, and two of them must agree; a third is asked if they disagree. BeamCore itself never reads the file. As a result:

  • the source is read two or three more times, which your storage provider may bill as egress;
  • the source must be readable over HTTPS through a signed URL, as S3 and R2 are. A Hugging Face or plain-HTTP source with no SHA-1 is refused before anything is written (box_trusted_digest_required);
  • the file can be at most about 13.8 GiB; a larger one fails with digest_source_too_large;
  • the transfer needs enough independent operators that support Box besides its uploaders. If Beam cannot find them within 5 minutes, the transfer fails with digest_workers_unavailable and leaves the Box folder as it was.

Storing the SHA-1 with the object avoids all four. Files under 20 MiB need no SHA-1 in advance: each is written in a single request, and Box checks it.

Limits

  • File size. Box's free plan caps files at 250 MB; paid plans allow larger files.
  • Uploads. A file under 20 MiB is written in one request. A larger one goes through a Box upload session, in parts of 8, 16 or 32 MiB that Box chooses.
  • Rate limits. Box allows about 240 upload requests per minute per Box user. Beam paces uploads per Box user and runs at most two upload sessions for a user at a time; further transfers wait for a session to free up rather than fail.

Costs

A Box transfer is priced like any transfer: 0.2 credit per transfer plus 0.02 credit per GB. Studio starts one transfer per file, so copying a folder of many small files costs at least 0.21 credit per file. A large file from a source without a SHA-1 is also read again by the SHA-1 check above.

Cancelling

Cancelling a Studio run, or a transfer, stops its uploads. Beam deletes the placeholder files it created and revokes every token it minted; a file that already existed keeps its earlier versions.

Decentralized distributed bandwidth infrastructure.

On this page