# Allow domain access Source: https://developer.box.com/guides/api-calls/allowing-domain-access To use the Box APIs it is important that your application and users have access to the following domains, where needed. ## File preview To enable file preview, your application might need to load javascript file from the Box content delivery network (CDN). This file is loaded from the following domains. * `api.box.com` * `boxcdn.net` * `boxcloud.com` * `dl2.boxcloud.com` to `dl20.boxcloud.com` ## File downloads The following API domains are used to download files via the Box API. * `api.box.com` to initially request a file to download * `dl.boxcloud.com` to actually download files for authenticated users * `public.boxcloud.com` to actually download files for unauthenticated users Ensuring access to these domains is only a first step to downloading a file. To download a file the users need to have proper access permissions and need to be fully authenticated where needed. ## File uploads The following API domains are used to upload files via the Box API. * `api.box.com` to start a file upload * `upload.app.box.com` and `upload.box.com` to upload the file to Box # API versioning strategy Source: https://developer.box.com/guides/api-calls/api-versioning-strategy Box provides versioning capabilities for selected API endpoints. The version control system guarantees seamless functioning of existing endpoint versions, even if Box introduces new ones. API versioning empowers Box to continually enhance its platform, while also offering third-party developers a reliable avenue for feature updates and deprecations. To stay informed about the forthcoming API modifications, monitor the Changelog and maintain a current email address in the Developer Console's App Info section. In 2024, Box introduced year-based API versioning. All endpoints available at the end of 2024 were assigned the version `2024.0`. **No action is required for API users to continue using Box APIs.** To make version-aware API calls, include the `box-version` header with the value `2024.0` in your requests. ## How Box API versioning works Box API supports versioning in `header`. To determine which version to use, look at the API reference and included sample requests. ### Versioning in `header` Box API processes the `box-version` header which should contain a valid version name. For example, when a client wants to get a list of all sign requests using version `2025.0`, the request should look like this: ```sh theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'box-version: 2025.0' \ --header 'Authorization: Bearer … ``` If the provided version is correct and supported by the endpoint, a response is sent to the client. If the endpoint is available in multiple versions, the response will include the `box-version` header, which indicates the version used to handle the request. Endpoints introduced after 2024 may return a `400` error code if the version is incorrect. More information about versioning errors can be found here. If your request doesn't include a version, the API defaults to the initial Box API version - `2024.0` - the version of endpoints available before year-based versioning was introduced. However, relying on this behavior is not recommended when adopting deprecated changes. To ensure consistency, always specify the API version, with each request. By making your application version-aware, you anchor it to a specific set of features, ensuring consistent behavior throughout the supported timeframe. ## Release schedule and naming convention Box can introduce a new breaking change to certain endpoints **once per year**, which results in a new API version. Introducing a new version of the Sign Request endpoint means that **all paths and HTTP methods** of an endpoint will support it. For example, if Sign Request endpoints receive a new version it will apply to all endpoints listed in the table: | Method | Request URL | Description | | - | - | - | | GET | `https://api.box.com/2.0/sign_requests/:id` | Retrieves specific sign request details. | | GET | `https://api.box.com/2.0/sign_requests/` | Retrieves all sign requests. | | POST | `https://api.box.com/2.0/sign_requests/` | Creates new sign requests. | | POST | `https://api.box.com/2.0/sign_requests/:id/resend` | Sends a specific sign request again. | | POST | `https://api.box.com/2.0/sign_requests/:id/cancel` | Cancels a specific sign request. | ### Naming convention New API versions are labeled according to the calendar year of their release. **Example**: If a new version of the Sign Requests endpoint is released in 2025, it will be named `2025.0`. Box can issue a new breaking change to API endpoints **once** per year, reserving the right to release an additional breaking change to address security or privacy concerns. In such cases, the new version will be incremented by one in the suffix. **Example**: If security issues need addressing in the previously released version `2025.0` of Sign Requests, the new version will be labeled `2025.1`. Each stable version is supported for a minimum of 12 months. This means that when a new version is released, the previous version becomes deprecated and will be available for use, but no new features will be added. It also means that a new version cannot be released sooner than every 12 months. We strongly recommend updating your apps to make requests to the latest stable API version. However, if your app uses a stable version that is no longer supported, then you will get a response with an HTTP error code `400 - Bad Request`. For details, see Versioning Errors. ### Endpoint versioning indication To keep you informed about the current API state, and improve the readability of the versioned API reference, the affected endpoints are marked with a pill based on the `x-stability-level` tag or `deprecated` attribute. An example of a beta pill used for API reference endpoints | Schema element | Pill name | Description | | - | - | - | | `x-stability-level: beta` | Beta | Endpoints marked with **beta**, are offered subject to Box’s Main Beta Agreement, meaning the available capabilities may change at any time. When the beta endpoint becomes stable, the **beta** indication is removed. | | `x-stability-level: stable` or no `x-stability-level` tag | Latest version | **Latest version** marks the most recent stable API version of an endpoint. | | `deprecated: true` | Deprecated | An endpoint is deprecated, which means it is still available for use, but no new features are added. Such an endpoint is annotated with the `deprecated` attribute set to `true`. | ## Versioning errors When using versioned API actions such as calling an incorrect API version in header or a deprecated version can lead to errors. For details on possible errors, see versioning errors. ## How Box SDK versioning works The versioning strategy applies only to [generated SDKs](/sdks-and-tools/#next-generation-sdks). Box SDKs support the **All Versions In** SDK approach. This means that every release of SDK provides access to all endpoints in any version which is currently live. All generated SDKs use manager's approach - they group all endpoints with the same domain in one manager. For example `FolderManager` contains methods to: `create_folder`, `get_folder_by_id`, `update_folder_by_id`, `delete_folder_by_id`, `get_folder_items` and `copy_folder`. This division is done based on the value of `x-box-tag` field, which is assigned to each method in Public API Service specification. It mostly corresponds to the root of the endpoint URL, but not necessarily. For example: `FolderManager` contains methods with `https://api.box.com/2.0/folders` root URL, but the same base URL is also used in some methods of `SharedLinkFoldersManager`. References to all managers are stored under one Box Client object. See an example of the endpoint's lifecycle: 1. Initial state (only one version is available). ```js theme={null} class FilesManager { async updateFileById( fileId: string, requestBody: UpdateFileByIdRequestBody, queryParams: UpdateFileByIdQueryParams, headers: UpdateFileByIdHeaders ): Promise < FileFull > {} } ``` 2. A new `v2025_0` version of the endpoint is introduced (previous version is deprecated). The SDK introduces a new method for each new version of an endpoint. These methods are stored in the same manager as the old ones, but their names and corresponding classes are suffixed with the version number. The old method is deprecated with a notice indicating the minimal maintenance date – this will be the date when the endpoint will be considered for end-of-life status. ```js theme={null} class FilesManager { /** * @deprecated This endpoint will be EOL'ed after 05-2026. */ async updateFileById( fileId: string, requestBody: UpdateFileByIdRequestBody, queryParams: UpdateFileByIdQueryParams, headers: UpdateFileByIdHeaders ): Promise {} async updateFileById_2025_0( fileId: string, requestBody: UpdateFileByIdRequestBody_2025_0, queryParams: UpdateFileByIdQueryParams_2025_0, headers: UpdateFileByIdHeaders_2025_0 ): Promise {} } ``` 3. The API endpoint is marked as End-of-Life (EOL) The SDK releases a breaking change release with removed end-of-life (EOL) endpoints. Ideally, we should group the end-of-life dates for all endpoints into one date per quarter to avoid releasing numerous new major versions of SDKs. ```js theme={null} class FilesManager { async updateFileById_2025_0( fileId: string, requestBody: UpdateFileByIdRequestBody_2025_0, queryParams: UpdateFileByIdQueryParams_2025_0, headers: UpdateFileByIdHeaders_2025_0 ): Promise < FileFull_2025_0 > {} } ``` ## Breaking vs non-breaking changes Breaking changes in the Box API occur within versioned releases, typically accompanied by a new major API version. Minor adjustments, which do not disrupt existing functionality, can be integrated into an existing API version. The following table lists both breaking and non-breaking changes. | API Change | Breaking change | | - | - | | New endpoints | No | | New [read-only](https://swagger.io/docs/specification/data-models/data-types/) or optional fields in request | No | | New required fields in request | Yes | | New string constant in request | Yes | | Deprecation | No | | Retired / End-of-Life endpoints | Yes | | Rename/reshape of a field, data type, or string constant | Yes | | More restrictive change to field validations | Yes | | Less restrictive change to field validations | No | | Changing HTTP status code returned by an operation | Yes | | Removing a declared property | Yes | | Removing or renaming APIs or API parameters | Yes | | Adding a required request header | Yes | | Adding more error codes | No | | Removing or modifying error codes | Yes | | Adding a member to an enumeration | Yes | The [oasdiff](https://github.com/Tufin/oasdiff) tool allows detecting most of the possible breaking changes. ## Forward compatibility of API responses Box may add new, optional, or read-only fields to API responses at any time without a version bump. These changes are considered non-breaking and require no advance notification. Client applications must be implemented to tolerate additional JSON fields and ignore fields they do not recognize. Strict response schema validation or deserialization that rejects unknown fields is discouraged, as it may cause integrations to break when Box introduces new fields. To ensure long-term stability: * Integrate only the documented fields your application explicitly requires * Do not assume API responses will exactly match a fixed schema * Configure JSON parsing libraries to ignore unmapped or unexpected properties ## AI agent configuration versioning AI agent versioning gives the developers more control over model version management and ensures consistent responses. For details, see AI agent configuration versioning guide. ## Support policy and deprecation information When new versions of the Box APIs and Box SDKs are released, earlier versions will be retired. Box marks a version as `deprecated` at least 24 months before retiring it. In other words, a deprecated version cannot become end-of-life sooner than after 24 months. Similarly, for individual APIs that are generally available (GA), Box declares an API as `deprecated` at least 24 months in advance of removing it from the GA version. When we increment the major version of the API (for example, from `2025.0` to `2026.0`), we're announcing that the current version (in this example, `2025.0`) is immediately deprecated and we'll no longer support it 24 months after the announcement. We might make exceptions to this policy for service security or health reliability issues. When an API is marked as deprecated, we strongly recommend that you migrate to the latest version as soon as possible. In some cases, we'll announce that new applications will have to start using the new APIs a short time after the original APIs are deprecated. When customer calls deprecated API endpoint, the response will contain a header: ```sh theme={null} Deprecation: date="Fri, 11 Nov 2026 23:59:59 GMT" Box-API-Deprecated-Reason: /reference/deprecated ``` The date tells clients when this version was marked as deprecated. ## Versioning considerations When building your request, consider the following: * Endpoints in version `2024.0` can be called without specifying the version in the `box-version` header. If no version is specified and the `2024.0` version of the called endpoint does not exist, the response will return an HTTP error code `400 - Bad Request`. * If the `box-version` version header is specified but the requested version does not exist, the response will return an HTTP error code `400 - Bad Request`. For details, see versioning errors. When Box deprecates a resource or a property of a resource in the API, the change is communicated in one or more of the following ways: * Calls that include the deprecated behavior return the response header `Box-API-Deprecated-Reason` and a link to get more information: ```sh theme={null} box-version: 2025.0 Deprecation: version="version", date="date" Box-API-Deprecated-Reason: /reference/deprecated ``` * A deprecation announcement is posted in the developer changelog. * The API reference is updated to identify the affected resource and any action you need to take. Affected endpoints are marked with **deprecated** pill. * If there is an imminent backwards-incompatible change that affects your app, then the contact email for your app might be contacted about the deprecation. ## Additional resources * API reference # Ensure consistency with headers Source: https://developer.box.com/guides/api-calls/ensure-consistency Some Box APIs support headers used to ensure consistency between your application and Box. ## `etag`, `if-match`, and `if-none-match` Many of the file system items (files or folders) that can be requested via the API return an `etag` value for the item. For example, a file resource returns an `etag` in the JSON response. ```sh theme={null} curl https://api.box.com/2.0/files/12345 \ -H "authorization: Bearer ACCESS_TOKEN" ``` ```json theme={null} { "id": 12345, "etag": 1, "type": "file", "sequence_id": 3, "name": "Contract.pdf", ... } ``` This `etag` can be used as the value of an `if-match` or `if-none-match` header to either ensure a resource hasn't changed since the `etag` value was received, or to prevent unnecessary downloads for items that haven't changed. For example, to fetch the same file only if it has changed, pass in the `etag` value in an `if-none-match` header. ```sh theme={null} curl https://api.box.com/2.0/files/12345 \ -H "authorization: Bearer ACCESS_TOKEN" \ -H "if-none-match: 1" ``` This API call would result in an empty response if the file had not changed. ## Ensure consistent changes The `if-match` header allows your application to ensure that no changes are made to items when another application or a user has made changes to the item since your application last inspected it. This helps ensure that changes aren't lost when two applications or users are changing items at the same time. The following endpoints support this header. | `if-match` capable endpoints | | | - | - | | `POST /files/:id/content` | Upload a new file version | | `PUT /files/:id` | Update a file's information | | `DELETE /files/:id` | Delete a file | | `PUT /folders/:id` | Update a folder's information | | `DELETE /folders/:id` | Delete a folder | | `PUT /web_links/:id` | Update a web link's information | | `DELETE /web_links/:id` | Delete a web link | The response of these API calls depends on the existence of the item, and whether the `etag` value matches the most recent version. | Item found? | Etag match? | HTTP status | | - | - | - | | Yes | Yes | 200 | | Yes | No | 412 | | No | Yes | 412 | | No | No | 404 | **Moving items** The `if-match` header cannot be used to prevent moving of files, folders, or web links. Instead, Box always ensures that the latest item is moved to the new location. ## Prevent unnecessary request downloads The `if-none-match` header allows your application to ensure that no information is downloaded for items that have not changed since your application last inspected it. This helps ensure no unnecessary information is downloaded, speeding up your application and saving on bandwidth. | `if-none-match` capable endpoints | | | - | - | | `GET /files/:id` | Get a file's information | | `GET /folders/:id` | Get a folder's information | | `GET /web_links/:id` | Get a web link's information | | `GET /shared_items` | Get a shared item's information | The response of these API calls depends on the existence of the item, and whether the `etag` value matches the most recent version. | Item found? | Etag match? | HTTP Status | | - | - | - | | Yes | Yes | 304 | | Yes | No | 200 | | No | Yes | 404 | | No | No | 404 | # API Calls Source: https://developer.box.com/guides/api-calls/index Learn how to make calls to the Box REST API, including authentication headers, common patterns, and platform insights. The Box API is a restful API that follows common HTTP standards where possible. The following guides take a look at some of the useful features and common mistakes that a developer can encounter when working with these APIs. ## Quick example Every API call requires an access token in the `Authorization` header. This example retrieves the current user's profile: ```bash theme={null} curl https://api.box.com/2.0/users/me \ -H "Authorization: Bearer ACCESS_TOKEN" ``` ## API calls insights Admins and co-admins can access the Platform Insights dashboard that provides information on the total number of API calls per application. See [Platform Insights][insights] and applications for details. [insights]: https://support.box.com/hc/en-us/articles/20738406915219-Platform-Insights # Language codes Source: https://developer.box.com/guides/api-calls/language-codes The Box API uses a modified version of the **ISO 639-1 Language Code** to specify a user's language. The following is a list of language codes used when creating or updating. | Language | Code | | - | - | | Bengali | `bn` | | Danish | `da` | | German | `de` | | English (US) | `en` | | English (UK) | `gb` | | English (Canada) | `e2` | | English (Australia) | `e3` | | Spanish (Latin America) | `s2` | | Spanish | `es` | | Finnish | `fi` | | French | `fr` | | French (Canada) | `f2` | | Hindi | `hi` | | Italian | `it` | | Japanese | `ja` | | Korean | `ko` | | Norwegian (Bokmal) | `nb` | | Dutch | `nl` | | Polish | `pl` | | Portuguese | `pt` | | Russian | `ru` | | Swedish | `sv` | | Turkish | `tr` | | Chinese (Simplified) | `zh` | | Chinese (Traditional) | `zt` | # Pagination overview Source: https://developer.box.com/guides/api-calls/pagination/index The Box API supports two ways to paginate collections. The most common way to paginate is through offset-based pagination which is often used where the list of items is of a fixed, predetermined length. In some cases an API endpoint supports marker-based pagination, either as an alternative to offset-based pagination or as a full replacement. Marker-based pagination is often used in cases where the length of the total set of items is either changing frequently, or where the total length might not be known upfront. # Marker-based Pagination Source: https://developer.box.com/guides/api-calls/pagination/marker-based APIs that use marker-based paging use the `marker` and `limit` query parameters to paginate through items in a collection. Marker-based pagination is often used in cases where the length of the total set of items is either changing frequently, or where the total length might not be known upfront. ## Paging To fetch the first page of entries in a collection the API needs to be called either without the `marker` parameter, or with the `marker` set to `0`. The `limit` parameter is optional. ```sh theme={null} curl https://api.box.com/2.0/folders/0/items?limit=100&usemarker=true&marker= \ -H "authorization: Bearer ACCESS_TOKEN" ``` APIs that support both offset-based pagination and marker-based pagination require the `usemarker` query parameter to be set to `true` to ensure marker-based pagination is used. To fetch the next page of entries the API needs to be called with a `marker` parameter that equals the value of the `next_marker` value that was received in the API response. ```sh theme={null} curl https://api.box.com/2.0/folders/0/items?marker=34332423&limit=100&usemarker=true \ -H "authorization: Bearer ACCESS_TOKEN" ``` The final page of items has been requested when the next `next_marker` value is `null` in the response object. At this point there are no more items to fetch. With marker-based paging there is no way to determine the total number of entries in a collection except by fetching them all. Applications should not retain the `next_marker` value long-term as the internal implementation of the markers may change over time. ## Marker & Limit The following query parameters are used to paginate a collection. | Query parameter | Type | Default | | | - | - | - | - | | `marker` | String | | The first position in the collection to return results from. This should be a value that was returned in a previous request. | | `limit` | Integer | Depends on API | The maximum number of entries to return. If the value exceeds the maximum, then the maximum value will be used. | | `usemarker` | Boolean | | An optional query parameter that can be used with API endpoints that support both types of pagination to select pagination type. Set to `true` to enforce marker-based pagination. | ## Collections When paginating collections, the API returns an object that contains the set of results as an array, as well as some information about the current page of results. | Field | Type | | | - | - | - | | `entries` | Array | The page of items for this page. This will be an empty array if there are no results. | | `next_marker` | String | The value that can be used as the `marker` value to fetch the next page of results. If this value is `null` or an empty string there are no more results to fetch. | | `limit` | Integer | The limit used for this page of results. This will be the same as the `limit` query parameter unless it exceeded the maximum value allowed for this API endpoint. | ## Example endpoints Some endpoints that support marker-based pagination are: * List items for a folder * List a file's collaborations * List all webhooks for a user * List all users in an enterprise * List all items in the trash # Box API offset-based pagination Source: https://developer.box.com/guides/api-calls/pagination/offset-based Page Box API collections with `offset` and `limit`. This includes limits of offset-based pagination, and when to use marker-based paging instead. APIs that use offset-based paging use the `offset` and `limit` query parameters to paginate through items in a collection. Offset-based pagination is often used where the list of items is of a fixed and predetermined length. ## Paging To fetch the first page of entries in a collection the API needs to be called either without the `offset` parameter, or with the `offset` set to `0`. The `limit` field is optional. ```sh theme={null} curl https://api.box.com/2.0/folders/0/items?offset=0&limit=100 \ -H "authorization: Bearer ACCESS_TOKEN" ``` To fetch the next page of entries the API needs to be called with an `offset` parameter that equals the sum of the previous `offset` value and limit returned in the previous result, `previous_offset + previous_limit`. ```sh theme={null} curl https://api.box.com/2.0/folders/0/items?offset=100&limit=100 \ -H "authorization: Bearer ACCESS_TOKEN" ``` Note that the `offset` should be increased by the previous `limit` and not by the size of the entries in the response array, as this may be less than the limit. Generally we advise using the value of the `limit` in the response object to increase the `offset` value. The final page of items has been requested when the next `offset` value exceeds the `total_count` value in the response object. At this point there are no more items to fetch. ## Offset & Limit The following query parameters are used to paginate a collection. | Query parameter | Type | Default | | | - | - | - | - | | `offset` | Integer | `0` | The (zero-based) offset of the first item returned in the collection. In a zero-based offset `0` is a correct value. | | `limit` | Integer | Depends on API | The maximum number of entries to return. If the value exceeds the maximum, then the maximum value will be used. | The maximum `offset` for offset-based pagination is `9999`. Marker-based pagination is recommended when a higher offset is needed. ## Collections When paginating collections, the API returns an object that contains the set of results as an array, as well as some information about the current page of results. | Field | Type | | | - | - | - | | `entries` | Array | The page of items for this page. This will be an empty array if there are no results. | | `offset` | Integer | The offset used for this page of results | | `limit` | Integer | The limit used for this page of results. This will be the same as the `limit` query parameter unless it exceeded the maximum value allowed for this API endpoint. | | `total_count` | Integer | One greater than the offset of the last item in the entire collection. The total number of items in the collection may be less than `total_count`. | ## Example endpoints Some endpoints that support offset-based pagination are: * List items for a folder * List a file's comments * List all items in the trash # Box API errors Source: https://developer.box.com/guides/api-calls/permissions-and-errors/common-errors Look up Box API HTTP errors, JSON error responses, and common 4xx and 5xx codes when a request fails. The Box API uses [HTTP status codes][status-codes] to communicate whether a request was processed successfully. Working through API errors? A free developer account gives you access to the Developer Console, where you can test API calls and debug with your own applications. ## Client error Most client errors in the HTTP 4XX range, and some server errors in the HTTP 5XX range, return a standard client error JSON object. ```json theme={null} { "type": "error", "status": 400, "code": "bad_digest", "help_url": "/guides/api-calls/permissions-and-errors/common-errors/", "message": "The specified content-md5 did not match what we received", "request_id": "abcdef123456" } ``` See the Client Error resource for more details. ## Common error codes Check our [Developer Troubleshooting Articles][articles] for solutions to common errors encountered when working with the Box APIs. ### 400 Bad Request | | | | - | - | | **Error** | `bad_digest` | | **Message** | The specified `content-md5` did not match what we received. | | **Solution** | While uploading a file, a `content-md5` header with the SHA-1 hash of the file can be supplied to ensure that the file is not corrupted in transit. The SHA-1 hash that was supplied in the request did not match what was received in the upload. Supply a valid SHA-1 hash of the uploaded file. | | | | | - | - | | **Error** | `bad_request` | | **Message** | | | **Solution** | Required parameters supplied in the API request are either missing or invalid. Check the extended error message in the response body for more details. | | | | | - | - | | **Error** | `cannot_make_collaborated_subfolder_private` | | **Message** | Cannot move a collaborated subfolder to a private folder unless `owned_by.id` matches the requestor’s user ID. | | **Solution** | Set the `owned_by.id` field to the user ID of the API requestor when moving a collaborated subfolder into a private folder. | | | | | - | - | | **Error** | `collaborations_not_available_on_root_folder` | | **Message** | Root folder cannot be collaborated | | **Solution** | You cannot set collaborators on a user's root folder (folder ID 0). Use a different folder ID than the root folder. | | | | | - | - | | **Error** | `cyclical_folder_structure` | | **Message** | Folder move creates cyclical folder structure | | **Solution** | The folder ID specified in the folder move would create a cyclical folder structure (for example moving a folder to a subfolder within itself). Change the folder ID for the folder move request. | | | | | - | - | | **Error** | `folder_not_empty` | | **Message** | Cannot delete – folder not empty | | **Solution** | Delete all content from the folder before attempting to delete it. | | | | | **Error** | `invalid_collaboration_item` | | **Message** | Item type must be specified and set to 'folder' | | **Solution** | Set the `item.type` field of the collaboration item to `folder`. | | | | | - | - | | **Error** | `invalid_client` | | **Message** | The client credentials are invalid. | | **Solution** | Verify the `client_id` and `client_secret` in the token request match the values in the Developer Console. | | | | | - | - | | **Error** | `invalid_grant` | | **Message** | Verify the authorization code is set correctly in your request, or your application likely needs to get a new authorization code. | | **Solution** | The authorization code supplied in the API request is missing or no longer valid. Possible solutions are to verify that the access token is added correctly in the request. If correctly set, the access token might have expired. Attempt to refresh the access token or fetch a new one. | | | | | - | - | | **Error** | `invalid_grant` | | **Message** | Current date time must be before the expiration date time listed in the `exp` claim. | | **Solution** | This error occurs when the Unix time on your local machine and the Box server are out of sync. To fix this error, update the Unix time on your machine to match a synchronized time server, then try the request again. | | | | | - | - | | **Error** | `invalid_grant` | | **Message** | Invalid refresh token. | | **Solution** | The refresh token might be invalid, revoked, or expired. Correct your application's refresh token handling and obtain a new token pair through the OAuth 2.0 authorization flow if needed. | | | | | - | - | | **Error** | `invalid_grant` | | **Message** | The authorization code has expired. | | **Solution** | Authorization codes expire shortly after they are issued (on the order of tens of seconds). Exchange the code for tokens immediately after the user is redirected back to your application. | | | | | - | - | | **Error** | `invalid_grant` | | **Message** | Please check the `sub` claim. | | **Solution** | For JWT authentication, set the `sub` (subject) claim to the correct user ID or enterprise ID depending on `box_sub_type`. More information is available on our [support site](https://support.box.com/hc/en-us/articles/360043691734). | | | | | - | - | | **Error** | `invalid_grant` | | **Message** | Please check the `jti` claim. A unique `jti` value is required. | | **Solution** | Ensure the JWT ID (`jti`) is set to a valid, unique value for each assertion. The same `jti` cannot be reused. | | | | | - | - | | **Error** | `invalid_grant` | | **Message** | Please check the `iss` claim. | | **Solution** | The issuer (`iss`) claim must match the OAuth client ID for your application when using JWT authentication. | | | | | - | - | | **Error** | `invalid_grant` | | **Message** | Signature verification error. The public key identified by `kid` must correspond to the private key used for signing. | | **Solution** | Use the public/private keypair associated with your app in the Developer Console. If you rotate keys, add the new key, remove the old one, update your configuration file with the new keypair and `kid`, then request a new access token. | | | | | - | - | | **Error** | `invalid_grant` | | **Message** | `kid` invalid, unable to lookup correct key. | | **Solution** | The key ID (`kid`) in the JWT header must match a public key registered for the application (for example, the `publicKeyID` in your configuration). Confirm you are using the correct configuration file, or generate a new RSA keypair in the Developer Console and update your app to use it. | | | | | - | - | | **Error** | `invalid_grant` | | **Message** | App is not yet authorized for use. | | **Solution** | The server authentication app (JWT or Client Credentials Grant) has not been authorized for use in your enterprise. Check the authorization status in the right-side panel on the app's **Configuration** tab. See [Platform app approval](/guides/authorization/platform-app-approval). | | | | | - | - | | **Error** | `invalid_grant` | | **Message** | Cannot obtain user token based on the enterprise configuration for your app. | | **Solution** | You are requesting a user token (for example, CCG with `box_subject_type=user`, or the `as-user` header) but the app is not configured to issue one. On the app's **Configuration** tab, enable **Generate User Access Tokens** under **Advanced Features**, set **App Access Level** to **App + Enterprise Access** if you are authenticating as a Managed User or Admin, then have an admin re-authorize the app. See [Client Credentials Grant](/guides/authentication/client-credentials). | | | | | - | - | | **Error** | `invalid_limit` | | **Message** | Limit is not a valid number | | **Solution** | Add a valid numeric value for the supplied limit value. | | | | | **Error** | `invalid_offset` | | **Message** | Offset is not a valid number | | **Solution** | Add a valid numeric value for the supplied offset value. | | | | | **Error** | `invalid_request` | | **Message** | The grant type is unauthorized for this client\_id. | | **Solution** | You might be requesting a token using standard OAuth 2.0 while the app is configured for Server Authentication (JWT), or the other way around. Use the token request type that matches your app's authentication method. See [The grant type is unauthorized for this client\_id](https://support.box.com/hc/en-us/articles/360044193033-API-Authentication-The-grant-type-is-unauthorized-for-this-client-id). | | | | | - | - | | **Error** | `invalid_request` | | **Message** | Invalid `grant_type` parameter or parameter missing. | | **Solution** | You might be sending token requests to the wrong domain (such as `app.box.com` or `www.box.com`). Send token requests to `https://api.box.com`. Use the `grant_type` and other parameters required for your flow (`authorization_code`, `refresh_token`, [JWT assertion](/reference/post-oauth2-token#body-grant-type), and so on). | | | | | - | - | | **Error** | `invalid_request` | | **Message** | Cannot obtain user token based on the enterprise configuration for your app. | | **Solution** | Your app might be missing a scope or configuration needed to request a user token. See [Cannot Obtain Token Based on Enterprise Configuration for Your App](https://support.box.com/hc/en-us/articles/360044192553). | | | | | - | - | | **Error** | `invalid_request_parameters` | | **Message** | Invalid input parameters in request | | **Solution** | Invalid parameters were sent in the API request. Check the API reference documentation for the correct request parameters that should be supplied for the API operation. | | | | | - | - | | **Error** | `invalid_status` | | **Message** | You can change the status only if the collaboration is pending | | **Solution** | The status of a collaboration can only be updated to accepted or rejected by the user specified in the `accessible_by` field when the current status is set to pending. | | | | | - | - | | **Error** | `invalid_upload_session_id` | | **Message** | The upload session ID provided in the URL is not of a valid format. | | **Solution** | The session ID supplied when making a chunked upload API request was invalid. Use the same session ID from the session that was created. | | | | | | - | - | - | | **Error** | `item_name_invalid` | | | **Message** | Item name invalid | | | **Solution** | Verify that the file's name is valid. Box only supports file or folder names that are 255 characters or less. File names containing non-printable characters, names containing the characters `/`, `\`, `<`, `>`, `:`, \` | `, `?`, `\*`, `-\`, names with leading or trailing spaces, and the special names "." and ".." are also unsupported. | | | | | - | - | | **Error** | `item_name_too_long` | | **Message** | Item name too long | | **Solution** | Shorten the length of the name that is being supplied for the item. The maximum length of a file or folder name in Box is 255 characters or less. | | | | | - | - | | **Error** | `metadata_after_file_contents` | | **Message** | Metadata is included after file contents in a file upload request. | | **Solution** | Include the file metadata before the file's contents. | | | | | **Error** | `password_reset_required` | | **Message** | User needs to reset password | | **Solution** | The user has not yet completed account [setup steps](https://support.box.com/hc/en-us/articles/360043691614). | | | | | **Error** | `requested_page_out_of_range` | | **Message** | Requested representation page out of range | | **Solution** | The range header supplied does not fit within the size of the specified item. Adjust the bounds to fit within the size of the item and try again. | | | | | - | - | | **Error** | `requested_preview_unavailable` | | **Message** | Requested preview unavailable | | **Solution** | The thumbnail size requested for the file is not valid. See the reference docs for the API operation for available format sizes. | | | | | - | - | | **Error** | `sync_item_move_failure` | | **Message** | Cannot move a synced item | | **Solution** | The item is set to be synced by the Box sync clients and cannot be moved. A possible solution is to set the `sync_state` of the item to `not_synced`. | | | | | - | - | | **Error** | `task_assignee_not_allowed` | | **Message** | Assigner does not have sufficient privileges to assign task to assignee | | **Solution** | The user who is attempting to assign the task does not have the appropriate permissions to do so. Adjust the user permissions to allow the assignment of tasks. | | | | | - | - | | **Error** | `terms_of_service_required` | | **Message** | User must accept custom terms of service before action can be taken | | **Solution** | The user in context for the API request has not accepted the enterprise's custom Terms of Service. For login-capable managed users, acceptance is required before most API calls, user access token issuance, or OAuth authorization can proceed. Service accounts and App Users are exempt. If you are using server authentication with the `As-User` header or a user access token, the impersonated or token subject user must have accepted. The user can accept via the Box web application, or your application can accept programmatically using the Terms of Service application flow. For more information, see [Using custom Terms of Service](https://support.box.com/hc/en-us/articles/360044192733-Using-Custom-Terms-Of-Service). | | | | | - | - | | **Error** | `unauthorized_client` | | **Message** | This app is not authorized by the enterprise admin. | | **Solution** | Server authentication applications using JWT or Client Credentials Grant must be authorized by a Box Admin before use. Follow the steps in Platform app approval. | | | | | - | - | | **Error** | `user_already_collaborator` | | **Message** | User is already a collaborator | | **Solution** | The user you are trying to add as a collaborator is already a collaborator on that item. You don't need to send this request. | ### 401 Unauthorized | | | | - | - | | **Error** | `unauthorized` | | **Message** | Unauthorized | | **Solution** | Authorization token is not authorized, check extended error message in body for more details. | | | | | - | - | | **Error** | `invalid_token` | | **Message** | The access token provided is invalid. | | **Solution** | The access token might be incorrect, corrupted, or expired – for example, because of a typo, using a token from another environment, or revocation or deletion. Obtain a new access token from the token endpoint. For OAuth 2.0 authentication, you can refresh an expired access token. See Refresh a token. | ### 403 Forbidden | | | | - | - | | **Error** | `access_denied_insufficient_permissions` | | **Message** | Access denied – insufficient permission | | **Solution** | The access token does not have the appropriate user permissions or scopes. For solutions, see [403 access denied insufficient permissions errors](https://support.box.com/hc/en-us/articles/360043693434-API-Content-API-403-access-denied-insufficient-permissions-Errors). | | | | | - | - | | **Error** | `insufficient_scope` | | **Message** | The request requires higher privileges than provided by the access token. | | **Solution** | This error is typically produced when scopes that are needed for the API operation are not enabled. Check your configured application scopes and reauthorize your application, if applicable. | | | | | - | - | | **Error** | `access_denied_item_locked` | | **Message** | Access denied – item locked | | **Solution** | You are attempting to access a locked item without appropriate permissions to access it. Unlock the item first, then try to access it again. | | | | | - | - | | **Error** | `access_from_location_blocked` | | **Message** | | | **Solution** | You're attempting to log in to Box from a location that has not been approved by your admin. Talk to your admin to resolve this issue. | | | | | - | - | | **Error** | `file_size_limit_exceeded` | | **Message** | File size exceeds the folder owner’s file size limit | | **Solution** | For limits based on account type, see [Understand the maximum file size you can upload](https://support.box.com/hc/en-us/articles/360043697314-Understand-the-Maximum-File-Size-You-Can-Upload-to-Box). | | | | | **Error** | `forbidden` | | **Message** | | | **Solution** | Client does not have permission to upload to this session. Only the user who initiated the upload session may upload to it. | | | | | - | - | | **Error** | `forbidden_by_policy` | | **Message** | Access denied – Blocked by Shield Access Policy | | **Solution** | Shield access policies applied on your enterprise have prevented this action. Contact your enterprise admin to adjust the applied Shield access policies. | | | | | - | - | | **Error** | `forbidden_by_policy` | | **Message** | Access denied – Blocked by Shield Malware Detection Rule | | **Solution** | An active Shield malware detection rule prevents download or local editing of potentially malicious content, but preview and online editing remain available. Contact your enterprise admin to adjust the applied Shield policies. | | | | | - | - | | **Error** | `incorrect_shared_item_password` | | **Message** | | | **Solution** | A password is required for the shared item, but it was either incorrect or not supplied. | | | | | - | - | | **Error** | `storage_limit_exceeded` | | **Message** | Account storage limit reached | | **Solution** | The storage limit of the account has been reached. Either [upgrade](https://support.box.com/hc/en-us/articles/360043692774-Upgrading-your-Box-Account) your account or permanently delete content to continue. Content moved to the trash still counts towards the account total until it is permanently deleted. | | | | | - | - | | **Error** | `user_email_confirmation_required` | | **Message** | User needs to complete email confirmation | | **Solution** | The user has not yet completed [steps](https://support.box.com/hc/en-us/articles/360043691614) for email confirmation. | | | | | **Error** | `cors_origin_not_whitelisted` | | **Message** | Access denied - Did you forget to safelist your origin in the CORS configuration of your app? | | **Solution** | Your application tried to access the Box API from a website. The application needs to explicitly allow Cross Origin Resource Sharing for the domain your site is hosted on. | ### 404 Not Found | | | | - | - | | **Error** | `not_found` | | **Message** | | | **Solution** | The resource could not be found. Check the extended error message in the response body for more details. | | | | | - | - | | **Error** | `not_trashed` | | **Message** | Item is not trashed | | **Solution** | The item that is to be permanently deleted is not in the trash. Send the item to the trash first. | | | | | - | - | | **Error** | `preview_cannot_be_generated` | | **Message** | Preview cannot be generated | | **Solution** | You are not able to generate a preview thumbnail for the specified file. | | | | | **Error** | `trashed` | | **Message** | Item is trashed | | **Solution** | The item that is to be accessed is in the trash and unavailable for modification. Move the item out of the trash and try again. | ### 405 Method Not Allowed | | | | - | - | | **Error** | `method_not_allowed` | | **Message** | Method Not Allowed | | **Solution** | The HTTP method used for the API operation is not allowed. Check the API reference documentation for the HTTP verb needed for the API operation. | ### 409 Conflict | | | | - | - | | **Error** | `conflict` | | **Message** | A resource with this value already exists | | **Solution** | This error can occur when the resource to be created already exists. Check the extended error message in the response body for more details. | | | | | - | - | | **Error** | `item_name_in_use` | | **Message** | Item with the same name already exists | | **Solution** | This error is produced when a resource with the same name already exists. Ensure that the resource name being added or modified is unique. | | | | | - | - | | **Error** | `name_temporarily_reserved` | | **Message** | The item name is reserved by another processing item. Wait 60 seconds and then retry the request, or wait and check the parent folder to see if the name is in use. | | **Solution** | Two duplicate requests have been submitted at the same time. Box acknowledges the first and reserves the name, but a second duplicate request arrives before the first request has completed. The name can stay reserved for up to 60 seconds. Wait at least 60 seconds before retrying. If the first request completed, a later retry can return `item_name_in_use` instead. | | | | | - | - | | **Error** | `operation_blocked_temporary` | | **Message** | The operation is blocked by another ongoing operation | | **Solution** | This error is returned when trying to access a folder that is blocked by another folder operation, such as a move or copy. Try again at a later interval. | | | | | - | - | | **Error** | `recent_similar_comment` | | **Message** | A similar comment has been made recently | | **Solution** | A similar comment was recently made, and the API has flagged it as a potential duplicate. Verify that the comment was indeed made, or modify the comment content and try again. | | | | | - | - | | **Error** | `user_login_already_used` | | **Message** | User with the specified login already exists | | **Solution** | A user with the same email already exists. Either refer to the existing user or specify a different email. | ### 410 Gone | | | | - | - | | **Error** | `session_expired` | | **Message** | | | **Solution** | The upload session associated with the given upload session ID has expired and can no longer be accessed. | | | | | - | - | | **Error** | `upload_failed` | | **Message** | | | **Solution** | The upload session is in an unrecoverable state and cannot continue. This or other requests have resulted in the upload session reaching a bad state (for example parts overlapping). Possible situations where this can occur include when the maximum number of parts has been exceeded or when overlapping parts have been uploaded. | ### 411 Length Required | | | | - | - | | **Error** | `length_required` | | **Message** | content-length header was required, but not provided. | | **Solution** | Supply a content-length header within your API request. | ### 412 Precondition Failed | | | | - | - | | **Error** | `precondition_failed` | | **Message** | The resource has been modified. Retrieve the resource again and retry | | **Solution** | Check the extended error message in the response body for more details. | | | | | - | - | | **Error** | `sync_state_precondition_failed` | | **Message** | The resource has been modified. Retrieve the resource again and retry | | **Solution** | Check the extended error message in the response body for more details. | ### 413 Request Entity Too Large | | | | - | - | | **Error** | `request_entity_too_large` | | **Message** | Request Entity too Large | | **Solution** | This error is produced when the size of the upload is more than the allowed maximum. Check the extended error message in the response body. | ### 415 Unsupported Media Type | | | | - | - | | **Error** | `unsupported_media_type` | | **Message** | Previews for `boxnote` files are not yet supported. | | **Solution** | This error is produced when requesting an embed preview of a Box Note. Embedded previews are currently unsupported for Box Notes. | ### 429 Too Many Requests | | | | - | - | | **Error** | `rate_limit_exceeded` | | **Message** | Request rate limit exceeded, try again later. | | **Solution** | The client is performing operations too quickly and has been rate limited. Client is advised to retry their request after the amount of time specified by the `retry-after` header. There are four rate limits to be aware of. | | | | | - | - | | **Error** | `rate_limit_exceeded` | | **Message** | Your enterprise has reached its monthly AI unit quota. Try another agent, contact your admin to purchase additional units, or wait for the quota to reset. | | **Solution** | Box AI endpoints return this error when your enterprise has used all of its AI Units. The response has no `retry-after` header, and retrying doesn't succeed until the quota resets or your admin purchases more AI Units. Box SDKs retry `429` responses automatically, so check the `message` or `help_url` to tell this error apart from a rate limit and stop retrying. See Understanding Box AI usage. | ### 500 Internal Server Error | | | | - | - | | **Error** | `internal_server_error` | | **Message** | Internal Server Error | | **Solution** | Client should retry using [exponential back-off strategy](https://en.wikipedia.org/wiki/Exponential_backoff) | ### 502 Bad Gateway | | | | - | - | | **Error** | `bad_gateway` | | **Message** | | | **Solution** | Client should retry using [exponential back-off strategy](https://en.wikipedia.org/wiki/Exponential_backoff) | ### 503 Unavailable | | | | - | - | | **Error** | `unavailable` | | **Message** | Unavailable | | **Solution** | If a Retry-After header is provided in the response, the client should retry the request according to the header value. In rare situations, a write operation might eventually persist its changes after the 503 response is received by the client, so the client should handle this case upon retry. If the issue persists, check our [Status Site](https://status.box.com/) for any known outage information. | [status-codes]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html [articles]: https://support.box.com/hc/en-us/sections/360007552913-Troubleshooting-Box-Platform # Token & URL Expiration Source: https://developer.box.com/guides/api-calls/permissions-and-errors/expiration Across the Box API there are a few tokens, codes, and URLs that automatically expire. The following is a quick overview of their respective expiration times. | | | | - | - | | [Authorization Codes] | Expires after 30 seconds | | [Access Tokens] | Expires after 60 minutes | | [Refresh Tokens] | Expires after 60 days or 1 use | | [Download URLs] | Expires after 15 minutes | See each respective guide for more details. [Authorization Codes]: /guides/authentication/oauth2 [Access Tokens]: /guides/authentication/tokens [Refresh Tokens]: /guides/authentication/tokens/refresh [Download URLs]: /guides/downloads # Box API rate limits Source: https://developer.box.com/guides/api-calls/permissions-and-errors/rate-limits Understand Box API rate limits, 429 responses, and how to retry requests without exceeding per-user and enterprise quotas. There are three common types of API call rate limitations that Box may use at its discretion to best protect network resources and preserve the quality of our customer experience. A free developer account lets you start making API calls and test your integration's rate limit handling with real requests. ## User based These rate limits protect our service from issues that may arise when a single user generates too much traffic. The number of API calls that a user can make in a minute is limited as described below. These limits apply to all Box user accounts and are the most common. Generally, they are initiated when a user exceeds approximately 1000 API calls/minute, but certain API endpoints may have different rate limits. ## Quality of service These rate limits are designed to protect the quality of service of our infrastructure. If there is resource contention in the infrastructure, we introduce automatic rate limits to prevent system degradation and outages. For instance, if an application happens to be accessing the same physical database server, such as the use of a file migration tool accessing related resources that access the same underlying physical resources, Box may impose temporary rate-limits when load spikes and adjust them as the system recovers. ## Licensing based All Box Business Plans come with a licensed number of permitted API calls per enterprise per month. These license based rate limits are designed to prevent excessive overages and misuse of network resources. If Box's infrastructure detects that a tool used by or on behalf of a customer has exceeded that customer's API license allocation or is intending to circumvent network controls, additional selective rate-limiting may be imposed. You can see the default API allocations licensed with a particular account level at our [pricing page][pricing], but note that some customers purchase Platform API Pricing plans that increase their allocation. ## Monitor and reduce API usage Calls from custom applications, scripts, and integrations count toward your enterprise's monthly allocation, including authentication requests. Calls from Box partner integrations and native Box applications don't. For the full definition, see the Platform Activity report. To find which applications drive your usage, an admin can review the Platform Insights dashboard or run a Platform Activity report. The report lists API calls per application and whether they are chargeable. To reduce the number of API calls your application makes: * Cache access tokens and reuse them until they expire. See authentication best practices. * Request only the fields you need with the `fields` query parameter. See request extra fields. * Use webhooks or the event stream instead of polling for changes. * Use marker-based pagination with a higher `limit` to retrieve large lists in fewer requests. * Use metadata queries to find content by metadata instead of reading metadata file by file. Box AI API calls consume AI Units, which are tracked separately from API calls. See Understanding Box AI usage. ## Per API rate limits There are currently a few distinct rate limits in place within the Box API. * General API calls * 1000 API requests per minute, per user * Uploads * 240 file upload requests per minute, per user * Search * 6 searches per second, per user, to the search endpoint * Two additional limits are applied on top of the basic rate limit * 60 searches per minute, per user * 12 searches per second, per enterprise * Box Sign * Create and resend sign request: 100 requests per minute, per user * Get sign request: 1000 requests per minute, per user ## Rate limit error When an application hits a rate limit, the API will return an API response with an HTTP status code of `429 Too Many Requests`. The response will include the following headers and JSON body. ```yaml theme={null} retry-after: 100 ``` ```json theme={null} { "type": "error", "status": 429, "code": "rate_limit_exceeded", "help_url": "/guides/api-calls/permissions-and-errors/common-errors/", "message": "Request rate limit exceeded, please try again later", "request_id": "abcdef123456" } ``` Please see the Client Error resource for more details. The `retry-after` header provides guidance on the number of seconds to wait before the next API call can be retried. In general, we advise using an exponential back-off strategy for retrying API calls. ## SDK retry behavior Box's current SDKs automatically retry requests that return `429 Too Many Requests`. The retry uses an exponential back-off strategy and honors the `retry-after` header returned by the API, so you don't need to implement your own retry loop for rate-limit errors when using these SDKs. See Box SDKs for the list of supported languages and versions. If you use an older Box SDK version, a community SDK, or call the API directly (for example, with `curl`, Postman, or your own HTTP client), you are responsible for retrying `429` responses yourself. Wait for the number of seconds indicated by the `retry-after` header before your first retry, then apply exponential back-off if the request continues to fail. [pricing]: https://www.box.com/pricing # Box API scopes Source: https://developer.box.com/guides/api-calls/permissions-and-errors/scopes Configure Box application scopes in the Developer Console so your app can call only the API endpoints it needs. When an application is created in the Developer Console, the user must configure application scopes. Similar to how users have permissions to access files and folders within Box, applications have their own set of permissions to perform certain actions on behalf of a Box user or a Box enterprise. The name for a set of permissions for an application is a "scope". In short, an application's scopes determine which endpoints an application can successfully call and are reflected in the access provided by Access Tokens of the application. ## User permissions and scopes Box API scopes are the permissions you assign to an application in the Developer Console. A call succeeds only when both the app's scopes and the authenticated user's permissions allow the action. It is important to understand that even if an application has the right scopes to perform an action, the user associated with the Access Token making the call needs to have permission to perform the action as well and vice versa. For example, if your application is set up to read files, the authenticated user does need to have permission to read the file you are trying to access. To learn more about how scopes, token permissions, and user permissions work together, see our security guide. A free developer account gives you access to the Developer Console, where you can configure application scopes and start making API calls. ## Scopes & OAuth 2 authorization When sending a user through a client-side OAuth 2 flow to authorize your application it is possible to append a set of scopes to the authorization URL to further restrict the user's access token. For example, if your application has the `root_readonly` and `root_readwrite` scopes enabled, it is possible to restrict a user's access token to `root_readonly` by specifying this scope when redirecting the user. ```js theme={null} GET https://account.box.com/api/oauth2/authorize?scope=root_readonly&client_id=.... ``` When the scope parameter is omitted the application will use the scopes that were set when the application was created. ## Self-service scopes These scopes are available through the Developer Console when configuring an application. Navigate to the **Application Scopes** section of the **Configuration** tab and select one or more of the following scopes. ### Read all files and folders | | | | - | - | | **OAuth Scope** | `root_readonly` | | **Application Scope** | Read all files and folders stored in Box. | Gives an application the ability to read all the files/folders for the authenticated user. Access to content is further restricted by the users' permission and Access Token used. Although this gives an application the permission to read files and folders, the user making the API call does need to have access to the items being accessed. In the case of a JWT application accessing a Managed User's items, the Service Account's Token will need to either use the `as-user` header or create a User Access Token to directly authenticate as the user who has access to the content. ### Read and write all files and folders | | | | - | - | | **OAuth Scope** | `root_readwrite` | | **Application Scope** | Read and write all files and folders stored in Box | Gives an application write access for the authenticated user. This allows the application to upload files or new file versions, download content, create new folders, update or delete collaborations, create comments or tasks, and more. Necessary to download files and folders. Access to content is further restricted by the users' permission and Access Token used. Although this gives an application read/write access to items, the user making the API call needs to have access to the content. ### Manage users The manage users scope in the Developer Console maps to two OAuth scopes. | | | | - | - | | **OAuth Scope** | `manage_managed_users` | | **Application Scope** | Manage users | Gives an application permission to manage Managed Users. It allows the app to change the user's primary login, reset their password, and change roles for managed users. Although this allows an application to manage users, for client-side applications, the Access Token used must be associated with an Admin or Co-Admin with the correct permissions. Additionally, for JWT applications, the application must be configured with **App Access + Enterprise Access** application access. | | | | - | - | | **OAuth Scope** | `manage_app_users` | | **Application Scope** | Manage users | Gives an application permission to manage App Users, which means this scope only applies to server-side authenticated (JWT) applications. ### Manage groups | | | | - | - | | **OAuth Scope** | `manage_groups` | | **Application Scope** | Manage groups | Gives an application permission to manage an enterprise's groups. It allows the app to create, update, and delete groups, as well as manage group membership. Although this allows an application to manage groups, for client-side applications, the Access Token used must be associated with an Admin or Co-Admin with the correct permissions. For JWT applications, groups the app creates itself can be managed with **App Access Only**. To manage groups that were not created by the app itself, the application must be configured with **App Access + Enterprise Access** application access. ### Manage webhooks | | | | - | - | | **OAuth Scope** | `manage_webhook` | | **Application Scope** | Manage webhooks | Gives an application permission to create webhooks for a user. Please review webhook limitations. Most notably, there is a limit of 1000 webhooks per application, per user. ### Manage enterprise properties | | | | - | - | | **OAuth Scope** | `manage_enterprise_properties` | | **Application Scope** | Manage enterprise properties | Gives an application permission to view the enterprise event stream, as well as view and edit the enterprise's attributes and reports. It also allows the application to edit and delete device pins. Although this allows an application to manage enterprise properties, for client-side applications, the Access Token used must be associated with an Admin or Co-Admin with the correct permissions. For example, to update a Shield list with the Update shield list endpoint, associate the Access Token with the primary admin, or with a co-admin who can manage Shield configurations. ### Manage retention policies | | | | - | - | | **OAuth Scope** | `manage_data_retention` | | **Application Scope** | Manage retention policies | | **Depends on** | `enterprise_content`-scope | Gives an application permission to view and create retention policies with Box Governance. This requires the enterprise to have purchased [Box Governance][governance]. This scope also requires the `enterprise_content` scope to function properly. These scopes can be requested by opening a ticket via our support channels. ### Manage signature requests | | | | - | - | | **OAuth Scope** | `sign_requests.readwrite` | | **Application Scope** | Manage signature requests | Gives an application permission to get, create, cancel, and resend sign requests. This scope requires the application to also have read/write scopes, which are automatically selected when enabled. In addition, an enterprise must have Sign enabled. ### Manage Box AI API | | | | - | - | | **OAuth Scope** | `ai.readwrite` | | **Application Scope** | Manage AI | Gives an application permission to send requests to Box AI API. ### Manage Box Relay | | | | - | - | | **OAuth Scope** | `manage_triggers` | | **Application Scope** | Manage Box Relay | Gives an application permission to interact with Box Relay endpoints and start flows of type `WORKFLOW_MANUAL_START`. This scope requires the application to also have read/write scopes. ## Available on request There are some additional scopes that are only available upon request. To do so, please submit a ticket to our [support team](/support). They will review these requests on an individual basis and only provide approval if the use case requires the scope. It is not possible to request extra scopes if your account is a free trial account. Before filing a support request for activation of the following scopes, log in to your paid enterprise account or [upgrade your free developer account][pricing] to an enterprise account tier. ### Manage Legal Holds | | | | - | - | | **OAuth Scope** | `manage_legal_holds` | | **Depends on** | `enterprise_content`-scope | Gives an application permission to create, view, edit, and delete legal hold policies and their assignments, apply policies to content, and manage file version retentions. For use with the Governance add-on. This scope depends on the `enterprise_content` scope to function properly. This scope can be requested by opening a ticket via our support channels. ### Suppress email notifications | | | | - | - | | **Application Scope** | Can suppress email notifications from API calls | Allows some types of email notifications to be suppressed when API calls are made. ### Global Content Manager (GCM) | | | | - | - | | **OAuth Scope** | `enterprise_content` | | **Application Scope** | Global Content Manager | Allows Admins, [Co-Admins][ca], and Service Accounts to retrieve any content they do not own or are not collaborators on within their enterprise, based on their enterprise settings. This scope is required to manage the retention policies and legal holds. **Side effects** Enabling this scope on an application changes the behavior of some API calls, and most notably, makes it impossible to write content without explicitly authenticating as a user using the `as-user` header. Additionally, enabling this scope disables accessing content that is owned by users in another enterprise. For this reason, this scope will not be provisioned unless absolutely necessary. ## Scopes for downscoping In some cases an Access Token needs to be downscoped to a more strict permission level, especially when a token needs to be exposed to a client-side, public environment like a browser. The primary example for this is when using [Box UI Elements][ui-elements], which require an Access Token in the user's browser. The following is a list of **additional** scopes that can be used with the `POST /oauth2/token` endpoint to downscope an existing access token. | OAuth Scope | UI Element affected | Description | | - | - | - | | `annotation_edit` | Preview | Create, edit, and delete annotations on a specific file, and view your own annotations on it. | | `annotation_view_all` | Preview | View annotations from all users on a specific file. | | `annotation_view_self` | Preview | View only the authenticated user's own annotations on a specific file. | | `base_explorer` | Explorer | Access content in the folder tree based on user/file/token permissions. | | `base_picker` | Picker | Access content in the folder tree based on user/file/token permissions. | | `base_preview` | Preview | Preview the file only. | | `base_sidebar` | Sidebar | Get basic file info needed for the sidebar UI element. | | `base_upload` | Uploader | Upload into the folder specified under `resource` when downscoping the token. | | `item_delete` | Explorer | Delete files, folders, and file versions, including permanently deleting items from trash. Applies only to the item the downscoped token was created for. | | `item_download` | Explorer, Preview | Download files or a folder's content. | | `item_preview` | Explorer | Preview a file. | | `item_rename` | Explorer | Rename files and folders. | | `item_share` | Explorer, Picker | Share the item specified under `resource` of the token exchange. | | `item_upload` | Picker | Upload in the content picker. | The standard OAuth scopes are also supported when downscoping. | OAuth Scope | Description | | - | - | | `ai.readwrite` | Send requests to Box AI API. | | `manage_managed_users` | Create, view, edit, and delete managed users, including changing a user's login, resetting their password, and assigning co-admin permissions. Also grants management of any managed user's content in the enterprise. | | `manage_app_users` | Create, view, edit, and delete app users, which are accounts created and owned by your application. For use with server-side authentication. Also grants the ability to activate and deactivate users, and the enterprise-wide user-listing permissions required to enumerate app users. | | `manage_data_retention` | Create, view, edit, and delete retention policies and their assignments, and remove file version retentions. For use with the Governance add-on. | | `manage_enterprise` | Broad administrative access to enterprise settings, user administration, admin event logs, group tags, and device pins. Grants a complete superset of both Manage users and Manage groups, plus co-admin permission assignment, content management for any user, terms of service management, and sandbox management. | | `manage_enterprise_properties` | Manage enterprise-level settings other than users and groups: enterprise attributes, admin event logs, device pins, enterprise feature settings (view and edit), terms of service, and the collaboration domain allowlist. Also grants view access to SSO settings and app authorizations, and the ability to manage sandboxes, manage platform apps, create automation users, create enterprise authorizations, and sign agreements. Requires App + Enterprise Access. | | `manage_groups` | Create, view, edit, and delete groups, manage group membership, and invite groups to collaborate. | | `manage_webhook` | Create, view, edit, and delete webhooks, and send notifications when content changes. Limited to 1000 webhooks per application, per user. | | `sign_requests.readwrite` | Interact with Box Sign endpoints. | [console]: https://app.box.com/developers/console [ui-elements]: https://github.com/box/box-ui-elements [pricing]: https://www.box.com/pricing/platform [governance]: https://www.box.com/security/governance-and-compliance [ca]: https://support.box.com/hc/en-us/articles/1500005433721-Users-Groups-Settings#h_01GSE1DYJKTY9EXEWJEDKFHCNV # Versioning errors Source: https://developer.box.com/guides/api-calls/permissions-and-errors/versioning-errors Box provides versioning capabilities for selected API endpoints. The version control system guarantees seamless functioning of existing endpoint versions, even if Box introduces new ones. API versioning empowers Box to continually enhance its platform, while also offering third-party developers a reliable avenue for feature updates and deprecations. To stay informed about the API modifications, monitor the [Changelog](/changelog) and maintain a current email address in the **App Info** section of the Developer Console. ## Error examples When using versioned API calls, you can encounter versioning-related errors. This reference lists the most common cases when errors appear and provides you with examples of such errors. ## Calling with incorrect `box-version` header If you call an API using an incorrect `box-version` header, the API will respond with an `HTTP error code 400 - Bad Request` error and provide the supported versions in the response message. The response will include one of the following status messages in `message` field: | Details | Message | | - | - | | `box-version` value is an unsupported API version or was sent malformed. | `Invalid API version specified in 'box-version' header.` | | The request headers did not include `box-version` header when versioned only endpoint was called. | `Missing required box-version header.` | | `box-version` is empty. | `Invalid (empty) API version specified in 'box-version' header.` | | `box-version` contained multiple versions. It requires **only one** version per request. | `The 'box-version' header supports only one header value per request, do not use comas.` | | An unsupported API version is used for an existing endpoint. | `Unsupported API version specified in 'box-version' header.` | An example of a response with an incorrect `box-version` header: ```json theme={null} { "type": "error", "status": 400, "code": "invalid_api_version", "help_url": "/reference/error-codes/invalid-api-version", "message": "Invalid API version specified in 'box-version' header. Supported API versions: [2024.0].", "request_id": "abcdef123456" } ``` ## Calling an incorrect API version in the URL Box documentation specifies API URLs. For instance, the Sign Requests endpoints are accessed via: `https://api.box.com/2.0/sign_requests/`. If you mistakenly make a call to an incorrect version, such as `https://api.box.com/3.0/sign_requests/`, the response returns an `HTTP error code 404 - Not Found` error. ## Calling a deprecated API When you use an API version that Box has marked as deprecated, the API will respond as usual. Additionally, it will append a `Deprecation` header, stating the deprecation date. For example: ```sh theme={null} Deprecation: date="Fri, 11 Nov 2026 23:59:59 GMT" Box-API-Deprecated-Reason: /reference/deprecated ``` You should monitor API responses to verify if the `Deprecation` header is present to accordingly plan the transition to a new API version. ## Calling a non-existent version If you attempt to use an outdated API version, such as `2025.0` which has reached its end-of-life, the response will return an `HTTP error code 404 - Not Found`. See [Calling an incorrect API version in the URL](#calling-an-incorrect-api-version-in-the-url) for more information. # Request extra fields Source: https://developer.box.com/guides/api-calls/request-extra-fields Learn about standard, mini, and full resource variants in the Box API, and how to use the fields query parameter to request additional fields. The number of fields returned for a resource depends on the API endpoint used to request the resource. ## Use the `fields` query parameter To request a specific field for a resource that is not returned by default in the standard response, append the `fields` query parameter to your request. The value of this parameter is a comma separated list of field names. ```sh theme={null} curl https://api.box.com/2.0/files/12345?fields=is_package,lock \ -H "authorization: Bearer ACCESS_TOKEN" ``` ```json theme={null} { "etag": "1", "id": "12345", "is_package": false, "lock": null, "type": "file" } ``` It is important to note that when a specific field is requested no other fields are returned except for those requested and the **base** set of fields. For a file, this base set is comprised of the `etag`, `id`, and `type` values. ## Resource variants The following resource variants are available in the Box API. ### Standard The default set of fields returned in an API response. The standard variant is returned when requesting a resource through the main APIs available for that resource. For example, when requesting the `GET /files/:id` endpoint the API will return the standard variation of a file. ```sh theme={null} curl https://api.box.com/2.0/files/12345 \ -H "authorization: Bearer ACCESS_TOKEN" ``` ```json theme={null} { "content_created_at": "2019-06-20T06:04:41-07:00", "content_modified_at": "2019-06-20T06:04:41-07:00", "created_at": "2019-06-20T07:28:42-07:00", "created_by": { "id": "191919191", "login": "joe@example.com", "name": "Joe Box", "type": "user" }, "description": "", "etag": "1", "file_version": { "id": "56663434454334", "sha1": "585afa5209bbd586c79499b7336601341ad06cce", "type": "file_version" }, "id": "12345", ... "size": 65000647, "trashed_at": null, "type": "file" } ``` ### Mini Where a resource is returned as a nested part of another response it is often reduced in size, only returning some of the more essential fields. This variant is commonly known as the mini resource variant. For example, when requesting the `GET /folders/:id/items` endpoint the API will return a mini variation of files and folders nested within the `item_collection`. ```sh theme={null} curl https://api.box.com/2.0/folders/12345/items \ -H "authorization: Bearer ACCESS_TOKEN" ``` ```json theme={null} { "id": "0", "type": "folder", "item_collection": { "entries": [ { "etag": "1", "file_version": { "id": "56663434454334", "sha1": "585afa5209bbd586c79499b7336601341ad06cce", "type": "file_version" }, "id": "12345", "name": "Video.mp4", "sequence_id": "1", "sha1": "585afa5209bbd586c79499b7336601341ad06cce", "type": "file" } ... ] ... } ... } ``` To request more information for a nested resource we recommend calling the API for that resource to request it by ID, and optionally pass along the `fields` query parameter. For example, to get the owner of a file returned when listing the items in a folder, request that file by ID with the query parameter `fields=owned_by`. ### Full The total set of fields that can be returned in an API response. The full variant is returned when requesting a resource through the main APIs available for that resource and by appending the `fields` query parameter. For example, when requesting the `GET /files/:id` endpoint with the `fields=is_package,lock` parameter the API will return the fields specified plus the basic fields for the file. ```sh theme={null} curl https://api.box.com/2.0/files/12345?fields=is_package,lock \ -H "authorization: Bearer ACCESS_TOKEN" ``` ```json theme={null} { "etag": "1", "id": "12345", "is_package": false, "lock": null, "type": "file" } ``` # Sorting responses Source: https://developer.box.com/guides/api-calls/sorting Where an API returns a collection of items it often supports sorting of API responses. Use the `sort` and `direction` query parameters to sort the collection either in ascending or descending order. ```sh theme={null} curl https://api.box.com/2.0/folders/0/items?sort=size&direction=DESC \ -H "authorization: Bearer ACCESS_TOKEN" ``` Not all API endpoints that return collections have support for sorting. Especially endpoints that use marker-based pagination often lack support for sorting the results. ## Sorting criteria The field to sort on is defined by the `sort` query parameter. Check the API endpoint's documentation for the possible options for this value. In some APIs the `sort` field is the second criteria by which the items are sorted. For example for the `GET /folders/:id/items` endpoint the results are always sorted by their type first before any other criteria. ## Sorting direction The sorting direction supports two values, either `ASC` for ascending order, or `DESC` for the reverse. # Status codes Source: https://developer.box.com/guides/api-calls/status-codes The following rules can be applied to interpret the HTTP status codes received when using the Box API. | HTTP Status | | | - | - | | `200-299` | Box received, understood, and accepted the API request. The request has either completed or is in the process of being completed. | | `300-399` | Box received, understood, and accepted the API request, yet the client must take further action in order to complete the request. Often this includes redirect to other URLs. | | `400-499` | A client error occurred when handling the request, often because the client either did not provide the right parameters, did not have access to the resources, or tried to perform an action that is otherwise not possible. | | `500-599` | Box received and accepted the request, but an error occurred within Box while handling it. These errors signify a problem with Box, not a problem with the client's request | # Suppress notifications Source: https://developer.box.com/guides/api-calls/suppress-notifications For some API calls, you can block email and webhook notifications by including a `box-notifications: off` header with the API call. ```sh cURL theme={null} curl -X POST https://api.box.com/2.0/folders \ -H "box-notifications: off" \ -H "authorization: Bearer ACCESS_TOKEN" \ -d '{ "name": "New Folder", "parent": { "id": "0" } }' ``` As an example, this can be used for a virus-scanning tool to download copies of every user's files in an enterprise without every collaborator on the file receiving an email informing them of the download. All actions will still appear in users' updates feed and the audit-logs. **Scope requirement** Notification suppression is available for approved applications only. Contact support to request the required scopes to be enabled for your application. The following settings need to be configured for your application for this feature to properly work. * **Can suppress email notifications from API calls** - available on request via support * **Manage Enterprise Properties** - available via the developer console * Co-admin permissions of **Edit settings for your company**. Some notifications can not be suppressed, most notably the creation of users, comments, collaborations, task assignments, and when changing a user's login. # Types and formats Source: https://developer.box.com/guides/api-calls/types-and-formats The following sections explain some basic concepts about the types and formats that can be encountered within the Box APIs. ## Requests The Box APIs use JSON in the request bodies. There are a few notable exceptions to this rule: * The `POST /oauth2/token` is used to request access tokens and as per the OAuth 2.0 specification it accepts the body to be sent with a content type of `application/x-www-form-urlencoded`. * Most of the APIs that are used to upload binary data, like the `POST /files/content` endpoint, expect data to be sent as form data with a content type of `multipart/form-data`. Although not required, we highly recommend passing a header with each API request to define the content type of the data sent, for example `content-type: application/json`. ### Headers As per the HTTP specification, all request header names in the Box API are case-insensitive and can be provided in lowercase, uppercase, or any mixed case form. In other words, the content type header can be set as `CONTENT-TYPE: application/json`, `content-type: application/json`, `content-type: application/json` or even the slightly absurd `cOnTeNt-TyPe: application/json`. Header values **are** mostly case sensitive unless stated otherwise. ### GZip compression By default data sent from Box is not compressed. To improve bandwidth and response times it's possible to compress the API responses by including an `Accept-Encoding: gzip, deflate` request header. ### Date and times The Box APIs support [RFC 3339][rfc3339] timestamps. The preferred way to format a date in a request is to convert the time to UTC, for example `2013-04-17T09:12:36-00:00`. In those cases where timestamps are rounded to a given day, the time component can be omitted. In this case, `2013-04-17T13:35:01+00:00` would become `2013-04-17`. In those cases where timestamps support millisecond precision the expected request format should be as follows `2013-04-17T09:12:36.123-00:00`. The timezone can differ between different files and folders because an enterprise's timezone can change over time. A common example is daylight saving time. Items created during standard time would have a different timezone than items created during daylight saving time. For this reason it's important to use an `RFC3339`-compliant date-time parser to handle dates returned by the API. Timestamps are restricted to dates after the start of the Unix epoch, `00:00:00 UTC` on January 1, 1970. ## Responses The Box APIs generally return JSON in the response body. There are a few notable exceptions to this rule as well. * APIs that delete items return an empty body with a `204 No Content` HTTP status code. * APIs used to request binary data either return a `200 OK` status code with the binary data attached, or a `202 Accepted`, or `302 Found` status code with no body and a `location` header pointing to the actual binary file. The `content-type` response header can be used to understand the type of content returned in the API. Additionally, every API endpoint has its response type documented in our API reference documentation. ### Headers As per the HTTP specification, all response header names in the Box API are case-insensitive and could change over time. This means that the API might return responses with a content type header of `CONTENT-TYPE: application/json`, `content-type: application/json` or `content-type: application/json`. Ideally your application should convert header names to a standard case upon request and then use that standardized set of headers to look up values of the headers. Header values **are** always case sensitive unless stated otherwise. ### Resources Most standard API responses where only one resource is returned follow the following format. ```json theme={null} { "id": "12345", "type": "folder", ... } ``` Every one of these resources will always return an ID and the type of the resource. ### Collections Where an API response returns multiple items a collection is returned. Although the exact format of these collections can change from endpoint to endpoint they generally are formatted as follows. ```json theme={null} { "total_count": 5000, "limit": 1000, "offset": 2000, "order": [ { "by": "type", "direction": "ASC" } ], "entries": [ { "id": 12345, "etag": 1, "type": "file", "sequence_id": 3, "name": "Contract.pdf" } ] } ``` | Field | Always present? | | | - | - | - | | `entries` | Yes | A list of entries in the collection | | `total_count` | No | The total numbers in the collection that can be requested. This can be larger than this page of results | | `limit` | No | For endpoints that support offset-based pagination, this specifies the limit to the number of results returned | | `offset` | No | For endpoints that support offset-based pagination, this specifies the offset of results returned | | `order` | No | For endpoints that support sorting, this specifies the order the results are returned in | | `next_marker` | No | For endpoints that support marker-based pagination, this specifies the marker for the next page that can be returned | | `prev_marker` | No | For endpoints that support marker-based pagination, this specifies the marker for the previous page that can be returned | ### Request IDs When your API call returns in an error, our API will return an error object with a `request_id` field. ```json theme={null} { "type": "error", "status": 400, "code": "item_name_invalid", "help_url": "/guides/api-calls/permissions-and-errors/common-errors/", "message": "Method Not Allowed", "request_id": "abcdef123456" } ``` When reaching out to support about a specific error, please provide the full API response including the `request_id` to help our support team to quickly find your request. Most API calls also return a `box-request-id` response header. The value of this header should not be confused with the `request_id` value in the body of an error response. ### Large numbers In some cases the API can return extremely large numbers for a field. For example, a folder's size might have grown to many terabytes of data and as a result the `size` field of the folder might have grown to a very large number. In these cases these numbers are returned in [IEEE754][numbers] format for example `1.2318237429383e+31`. [numbers]: https://en.wikipedia.org/wiki/IEEE_754 [rfc3339]: https://www.ietf.org/rfc/rfc3339.txt # Applications Source: https://developer.box.com/guides/applications/index Box Developer Console allows you to create applications you can then use to integrate with Box. **My Platform Apps** view displays a list of already created applications and gives you quick access to their configuration details. This way, you don't need to open the app each time you want to generate a Developer Token, copy the Client ID, or generate a report. ## Features **My Platform Apps** page allows you to: * Search through the list of already created apps. * Filter the apps by **Enablement Status** and **Authentication Type**. * Create a new app. * Copy the app's Client ID. * Rename the app and access its details with one click. * Check application enablement and authorization status. Apps published to Integrations display status from Integrations. The **Options menu** available for every entry allows you to: * Access the configuration details of your application. * Generate a Developer Token. * Add collaborators to your application. * Run the Platform App Diagnostics Report. ## Platform App Insights Admins and co-admins can access the Platform Insights dashboard that provides a comprehensive view of the organization’s platform usage. This includes app-related data, such as: * The total number of API calls per application. * A list of top applications within the enterprise. * A list of pending application approvals. * A list of applications awaiting enablement. See [Platform Insights][insights] for details. You need the following permissions to access and view Platform Insights: * View settings and apps for your company * Edit settings and apps for your company * Run new reports and access existing reports [insights]: https://support.box.com/hc/en-us/articles/20738406915219-Platform-Insights # Integrations Source: https://developer.box.com/guides/applications/integrations/index [Box Integrations][app-center] is the first place for Box users to find out about applications they can use in combination with Box. If your application can be used by other enterprises, listing your service under **Integrations** can be a great way to find new users. Integrations group apps into sections so that you can quickly find featured, most popular, or recently added apps. Integrations ## Developing a platform app or becoming a Box Partner If you need more information on developing a platform app for the Box Integrations or becoming a Box Partner, visit our [Box Partner Resources][bp] guides on our community site. ## Publishing a platform app Use the following steps to publish a platform app in Box Integrations. ### Prerequisites Your application must meet the following requirements: * The platform app is in a finished state and ready for production usage. * The platform app leverages OAuth 2.0 authentication, as Integrations do not support any other authentication methods. * You are a developer with access to the platform app in the **Developer Console**. ### Steps 1. Navigate to the Developer Console > **My Platform Apps** and select the app you want to publish. 2. Select the **Publishing** tab from the top menu. Publishing tab for an application 3. Read through the submission checklist and check the confirmation checkbox if your app meets all the requirements. 4. Fill in the form by providing the following details: * The categories your app falls under * A short and a long description * The URL for the **Add** button * Screenshots and an app icon * Supplementary information to support users 5. Use the **Preview** button in the top right corner to see how your application will look when listed. 6. Finally, submit the application for approval by clicking the **Submit for Approval** button. Once a request for approval is received, the Box Partner team will be notified and review your request as soon as possible. For any questions, email [`integrate@box.com`][email]. ## Unpublishing a platform app Once approved and published, a platform app can be unpublished from the same control panel: 1. Navigate to the **Developer Console** and select your platform app. 2. Select the **Publishing** tab. 3. You can now unpublish the app. [app-center]: https://app.box.com/services [email]: mailto:integrate@box.com [bp]: https://support.box.com/hc/en-us/sections/21356597387539-Box-Partner-Programs # Create a Platform App Source: https://developer.box.com/guides/applications/platform-apps/create A Platform App allows for interaction with our 150+ endpoints. For example, downloading/uploading, searching, applying metadata and more. ## Prerequisites Access to the [Developer Console][dev-console]. ## Create a Platform App 1. Navigate to the [Developer Console][dev-console]. 2. Click **New App**. 3. Select an app type: **User** for apps where users link their Box accounts, or **Server** for backend services infrastructure. 4. If you selected **Server**, you may also be asked to choose an authentication method (see below for more information). 5. Click **Create**. For **Server** apps, Client Credentials Grant is always the default. The **Switch server app auth type (CCG or JWT)** setting in **Admin Console > Enterprise Settings > Platform Apps** controls whether the method is locked: * When switching is **enabled** (the default for free developer accounts), the app is created with Client Credentials Grant and you can change it to JWT - or back - afterward. * When switching is **disabled** (the default for enterprises), the dialog shows a **Select Method** step with Client Credentials Grant preselected. You have the option to pick JWT at creation instead. The method is then fixed for the life of the app. See Choosing between CCG and JWT for guidance on picking the method, and Switch between CCG and JWT for how to change it later. ## Configure application settings After you create a Platform App, the settings screen is displayed. ### General settings * **App Name** - the name you set up during the app creation, you can change it here if needed. * **App Description** - provide a description for your app (optional). * **Contact Email** - this is set to the developer of the application by default. Keep in mind that once you publish your app, this email is publicly visible to Box users who view your app in the Integrations. We recommend changing it to a support email address, so that users can reach out to support in case of any issues with the integration. ### Configuration * **Purpose** - select the purpose of your app from the drop-down list. Depending on the option you choose, you might need to specify further details. | Purpose | Details | | - | - | | **Automation**, **Custom Portal** | Specify if the app is built by a customer or partner. | | **Integration** | Specify the integration category, external system name if you're integrating with one, and if the app is built by a customer or partner. | | *Other* | Specify the app purpose and if it is built by a customer or partner. | * **Authentication Method** - how your app authenticates to the Box APIs. | App type | Auth method | Details | | - | - | - | | **User Authentication** | OAuth 2.0 | Specify the client ID and client secret. | | **Server Authentication** | Client Credentials Grant or JWT | Chosen during app creation or set in the **Configuration** tab, depending on the **Switch server app auth type (CCG or JWT)** enterprise setting. See Switch between CCG and JWT. | * **Developer Token** - a developer token is created automatically when you create a Platform App. * **Application Scopes** - choose the scopes you want to grant to your app. See the scopes guide for detailed information on each option. * **Advanced Features** - enable the advanced features your application requires. * **CORS Domains** - add the domains you want to allow requests from. [dev-console]: https://app.box.com/developers/console # Platform App Source: https://developer.box.com/guides/applications/platform-apps/index A Platform App typically presents Box functionality to a user through a custom interface. Box offers pre-built, customizable user interface components, known as UI Elements, for functionalities like browsing, searching, and previewing content. ## Authentication methods When you create a Platform App, you select an app type: * **User**: for user-facing apps that redirect users to Box to log in. Uses OAuth 2.0. * **Server**: for backend services and automations. Uses Client Credentials Grant or JWT. Depending on your enterprise settings, you pick the method when you create the app or set it later in the **Configuration** tab. ## When to use Use a Platform App when you want to: * Use OAuth 2.0, Client Credentials Grant, or JWT for authentication. * Upload and download files * Access both your own files and files owned by managed or external users. * List the application in the Box Integrations * Provide integration into the Box Web App ## Use cases Example use cases include: * A file vault in an application that allows an end user to access files that have been shared with them, while also providing access for employees to the same files through the Box Web app. An example of this is a financial advisor sharing statements and investment prospectuses with investors that can be viewed and commented on within a platform application. * A file upload feature in an application that allows an end user to submit and upload files from within a custom-built application to Box. These uploads then initiate a business process with the Box Web app. An example of this is a candidate submitting a PDF of a resume to a recruiting portal that can then be routed to an appropriate employee for review. A free developer account gives you access to the Developer Console, where you can create Platform Apps using your preferred authentication method. ## Approval Platform Apps may require approval before use. # Create Web App Integration Source: https://developer.box.com/guides/applications/web-app-integrations/configure This guide explains how to set up a Web App Integration with a Platform App. Server-side integration is no longer supported. This means the applications using server-side actions will still be working, but you won't be able to modify the server-side configuration options such as Preliminary Callback URL or Basic Authentication. You will be able to deactivate them and change the implementation to a new one. ## Create an OAuth 2.0 Application Navigate to the [Developer Console][devconsole] and create a Platform App that leverages OAuth 2.0 authentication. ## Create a New Integration Then, navigate to the **Integrations** tab and click **Create a Web App Integration**. Integration Tab ## Configure Integration To configure the integration, follow the guidance below for each value. ### App Info | Field | Description | | - | - | | Integration name | The name of your integration, which users see in the Box Web App when they select the **More Options** > **Integrations** menu on a file or folder. | | Description | The description of the integration displayed in the Box Integrations. | | Supported file extensions | The integration will only appear as an option in the **More Options** > **Integrations** menu for the selected file extensions. | | Permissions requirement | Determines what permissions level users need to see the integration. **Download permissions are required** allows users to download the file - they will not be able to update it. **Full permissions are required** allows users to download and update the file. | | Integration scopes | Specifies the scope of your integration - either the file/folder from which integration is invoked, or its parent folder. | | Display on shared pages toggle | Determines if an integration can be shown to external users on a shared page. If enabled, users who are not collaborating on the content will see the integration in the context-menu when accessing the items through a shared link. | | Lock to only allow the current user to overwrite the file using your integration toggle | Determines if different web app integrations can edit the file at the same time. | | Integration type | Select desired integration type. Available options are: **Files**, **Folders**, **Both**. | ### Callback Configuration | Field | Description | | - | - | | Client Callback URL | Handles additional callback requests from Box after the primary request with Popup Integrations. If the application specifies a file parameter in the REST method, the preliminary callback URL cannot originate from the client. As a result, a second request must be made from the client to your server so the server can send the necessary interface to the user. | | Prompt Message | Specifies the message that users see when they initiate the integration. Use this field to provide context about what happens next. The message is limited to 500 characters. | | User Experience | Informs that the integration will open in a new window. | | New Window Settings | Determines if the application opens in a new tab. | ### Callback Parameters The **Callback Parameters** section configures the parameters that Box sends to the callback URL when a user accepts a confirmation prompt. If this setting is not configured, Box does not send any parameters to the callback URL. To add a parameter, select the **Method** (GET or POST), specify the **Parameter name** and add a **Parameter value**. The **File** method is no longer supported. If you already used this method, you cannot edit its values. You can change the **File** method to **GET** or **POST**, but you can't undo this action. For example: **GET - `userid` - `#user_id#`**. The following parameter values are available. | Parameter | Method | Description | | - | - | - | | `user_id` | GET, POST | The Box user ID. This information is used in Popup Integrations in which user authentication is required to complete an action. You can store the Box ID in your application to enable subsequent authentication requests from the integration. | | `user_name` | POST | The full name or email address of the Box user. Not all Box users specify their names at all times. | | `file_id` | GET, POST | The Box file ID. You can use this ID to make Box API calls that affect the file. | | `file_name` | POST | The name of the file. | | `file_extension` | GET, POST | The extension of the file. | | `auth_code` | GET, POST | The OAuth 2.0 authorization code, which is generated by Box upon successful authentication. Your application must then supply this authorization code to Box in exchange for an OAuth 2.0 Access Token. An authorization header containing a valid Access Token must be included in every Box API request. | | `redirect_to_box_url` | GET, POST | In Popup Integrations, the URL to which requests are sent by the confirmation prompt. Use this URL to redirect users to the All Files page. This parameter closes the popup panel and refreshes the All Files page to reflect any changes performed by the integration. If you do not want to add this parameter to your application, you can specify the entire URL. **Success**: `#redirect_to_box_url#&status=success&message=Your%20action%20was%20successful%2E`. **Failure**: `#redirect_to_box_url#&status=failure&message=Your%20action%20was%20unsuccessful%2E` | ### Integration Status * **Development**: The integration is visible and available only to application collaborators listed under the **General Settings** tab. This option is best used when the application is still in development and undergoing testing. * **Online**: The integration is visible and available to all Box users. This option is best used when development is complete and the application is ready to publish in the Integrations. * **Maintenance**: The integration is visible and available only to application collaborators listed under the **General Settings** tab. This option is best used after the integration is published in the Integrations, but needs to perform maintenance updates or troubleshoot issues. Use this option to temporarily take the integration offline for everyone except the application's collaborators. ## Example Use Cases of Box Integrations When a user chooses a Popup Integration, Box sends a callback request to the primary callback URL. It sends the callback parameters that have been configured to the server. In some cases, Box may make a second request if the client cannot get all the data it needs from the first request. The following example does not require a client callback URL: * The Popup Integration performs a REST call using a `download_file_url` callback parameter. * The user clicks **OK** in the confirmation prompt to accept the popup. * Box sends a request to the following URL (the primary callback URL plus the callback parameter): `http://www.doceditor.com/service?apikey=abc&file=&redirect=`. * The response from the callback URL displays a user interface to the user who made the request. The popup has all the information needed to continue the action and an additional client callback is not needed. The following example requires a client callback URL: * The Popup Integration performs a REST call using a file-callback parameter. * The user clicks **OK** in the confirmation prompt to accept the popup. * The popup displays a page where Box sends a POST request with the contents of a file, along with the callback parameters to the remote server. * Box receives the response from the remote server and directs the client to POST the response to the client callback URL. The server identified by the URL interprets the response and redirects the user with the correct session ID. ## Client-callback URL Request Format The POST request that Box sends to the client callback URL takes the response from the primary callback URL and forwards it to the same URL along with the same data as the original callback. | Client Callback URL | Example | | - | - | | Two GET parameters and one POST parameter: `http://your-client-callback-url.com/?get_param1=value1&get_param2=value2` | `POST data: post_param1=value1initial_callback_response` | The response to the client-callback request is an HTTP status 302, redirecting the user to the correct URL or to the HTML for a UI. Most often the URL points to a separate API or custom script developed for Web App Integrations, which parses the result of the primary callback URL. If you want to publish your platform app for all Box customers to use, make sure that the URL is publicly accessible on the internet. ## Making Integration Publicly Available To make a Box integration publicly available it needs to be listed in the App Center. Follow the Integrations guide for more details. [devconsole]: https://app.box.com/developers/console [devaccount]: https://account.box.com/signup/n/developer # Web App Integration Source: https://developer.box.com/guides/applications/web-app-integrations/index Web App Integrations allow third-party applications to become part of the Box user experience by allowing users to use such third-party applications when editing or sharing files. ## Features * **File interaction**. Users can modify, share, or edit content stored in Box using a third-party application. * **Recommended Apps support**. Integrations can appear in the Box Preview interface under **Recommended Apps**. For details, see [Recommended Web Integrations][recommended-web-integrations]. Integration example * **Scoped availability**. Integrations can be restricted to certain content types and file extensions. ## Visibility in Recommended Apps Your web application integration appears in **Recommended Apps** only if it is published in Integrations. [integrations]: /guides/applications/integrations [custom-app]: /guides/authentication/oauth2/oauth2-setup [oauth2]: /guides/authentication/oauth2 [devconsole]: https://app.box.com/developers/console [recommended-web-integrations]: https://support.box.com/hc/en-us/articles/360044195533-Installing-Recommended-Apps-in-your-Enterprise # Integrations Types Source: https://developer.box.com/guides/applications/web-app-integrations/types Currently, Box provides the Popup integration type. ## Popup Integrations In a popup integration, Box opens a panel and loads the application's callback URL configured for the integration. The application can display its own user interface for the integration in the popup. The integration receives a short-lived authorization code with this request, which can be used to connect to the Box APIs, exchange the code for an Access Token, and then use that to make API calls to Box. Popup panels use HTML ` ``` For in-person signing on the requester's device, also set `is_in_person` to `true`. For more information, see [In-person signing](/guides/box-sign/in-person-signing). For more information on Box Embed, see [Embed Box elements programmatically](/guides/embed/box-embed#programmatically). # In-person signing Source: https://developer.box.com/guides/box-sign/in-person-signing Let a signer complete a Box Sign request on the requester's device, and cycle through several in-person signers in one session. Set `is_in_person` to `true` when the requester and signer are together and the signer should complete the request on the requester's device. Set this on the signer when you [create the request](/guides/box-sign/create-sign-request). Also set `embed_url_external_user_id` so the response includes `embed_url` and `iframeable_embed_url`. Your app opens or embeds that URL to start the signing session. For more information, see [Embed the signing client](/guides/box-sign/how-signers-get-the-request#embed-the-signing-client). In-person signing cycles through each signer ## Create an in-person request ```sh theme={null} curl -i -X POST "https://api.box.com/2.0/sign_requests" \ -H "authorization: Bearer " \ -H "content-type: application/json" \ -d '{ "template_id": "ee9a689e-96b6-4076-92a0-b9b765eb09ca", "parent_folder": { "type": "folder", "id": "0987654321" }, "signers": [ { "email": "signer-a@example.com", "role": "signer", "is_in_person": true, "embed_url_external_user_id": "1234" }, { "email": "signer-b@example.com", "role": "signer", "is_in_person": true } ] }' ``` The first in-person signer gets the `embed_url` and `iframeable_embed_url`. Later in-person signers in the same session do not need their own `embed_url_external_user_id`. ## Several signers in one session After the first in-person signer finishes, the interface moves to the next in-person signer. The completed document is emailed to each signer. First in-person signer in the Box Sign client Second in-person signer after the first signer finishes # Box Sign Source: https://developer.box.com/guides/box-sign/index Send documents for electronic signature from your own app using the Box Sign API, and track them through to a signed file in Box. Box Sign brings the full Box Sign web app experience to your application through the API. Your app chooses the document, decides who signs it and in what order, and receives the signed file and a signing log back in a Box folder. ## How a signature request works Every integration follows the same path: 1. **Pick a document.** It can be a plain file that needs preparation, a file that already carries signature tags, or a template you built in the Box web app. See [document types](/guides/box-sign/document-types). 2. **Create the request.** Name the file, a destination folder, and at least one signer, along with any options you need. See [create a Box Sign request](/guides/box-sign/create-sign-request). 3. **Get the request to the signers.** Let Box email them, send the link yourself, or embed the signing client in your app. See [deliver the request to signers](/guides/box-sign/how-signers-get-the-request). 4. **Track it to completion.** Requests are asynchronous, so watch the [request status](/guides/box-sign/request-status) or subscribe to [webhooks](/guides/box-sign/webhooks). When everyone has signed, Box stores the signed `.pdf` and the signing log in the destination folder. ## Where to start If you are new to Box Sign, [set up your account and scopes](/guides/box-sign/setup), then send one request end to end with the [quick start](/guides/box-sign/quick-start). If you are scoping an integration, the [endpoints page](/guides/box-sign/api-endpoints) shows the whole API surface on one screen. ## What you can configure Most of what makes a signature request specific to your use case is set when you create it: * [Multiple signers and signing order](/guides/box-sign/multiple-signers) for approvals and sequential signing * [Signer verification](/guides/box-sign/signer-verification) for smart card, SMS, login, or password checks * [In-person signing](/guides/box-sign/in-person-signing) when the signer is standing next to the requester * [21 CFR Part 11 requests](/guides/box-sign/cfr-part-11) for GxP workflows Language, expiration, and redirect options are covered on the [create a Box Sign request](/guides/box-sign/create-sign-request) page. # List and get Box Sign requests Source: https://developer.box.com/guides/box-sign/list-sign-requests List all Box Sign requests for the current user, or get a single request by ID. ## List all requests Use the [get sign requests endpoint](/reference/get-sign-requests) to view a list of all Box Sign requests created by the user associated with the passed Access Token. ```sh cURL theme={null} curl -i -X GET "https://api.box.com/2.0/sign_requests" \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.signRequests.getSignRequests(); ``` ```python Python v10 theme={null} client.sign_requests.get_sign_requests() ``` ```cs .NET v10 theme={null} await client.SignRequests.GetSignRequestsAsync(); ``` ```swift Swift v10 theme={null} try await client.signRequests.getSignRequests() ``` ```java Java v10 theme={null} client.getSignRequests().getSignRequests() ``` ```java Java v5 theme={null} Iterable signRequests = BoxSignRequest.getAll(api); for (BoxSignRequest.Info signRequestInfo : signRequests) { // Do something with each `signRequestInfo`. } ``` ```python Python v4 theme={null} sign_requests = client.get_sign_requests() for sign_request in sign_requests: print(f'(Sign Request ID: {sign_request.id})') ``` ```cs .NET v6 theme={null} BoxCollectionMarkerBased signRequests = await client.SignRequestsManager.GetSignRequestsAsync(); ``` ```javascript Node v4 theme={null} const result = await client.signRequests.getAll(); console.log(`There are ${result.count} sign requests`); ``` ## Get a request by ID Use the [get sign requests by ID endpoint](/reference/get-sign-requests-id) to view information about a specific Box Sign request. This endpoint requires the sign request's ID, which can be obtained by using the [get all Box Sign requests endpoint](/reference/get-sign-requests) or in the response when [creating a Box Sign request](/reference/post-sign-requests). ```sh cURL theme={null} curl -i -X GET "https://api.box.com/2.0/sign_requests/" \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.signRequests.getSignRequestById(createdSignRequest.id!); ``` ```python Python v10 theme={null} client.sign_requests.get_sign_request_by_id(created_sign_request.id) ``` ```cs .NET v10 theme={null} await client.SignRequests.GetSignRequestByIdAsync(signRequestId: NullableUtils.Unwrap(createdSignRequest.Id)); ``` ```swift Swift v10 theme={null} try await client.signRequests.getSignRequestById(signRequestId: createdSignRequest.id!) ``` ```java Java v10 theme={null} client.getSignRequests().getSignRequestById(createdSignRequest.getId()) ``` ```java Java v5 theme={null} BoxSignRequest signRequest = new BoxSignRequest(api, id); BoxSignRequest.Info signRequestInfo = signRequest.getInfo(); //using `fields` parameter BoxSignRequest.Info signRequestInfoWithFields = signRequest.getInfo("status") ``` ```python Python v4 theme={null} sign_request = client.sign_request(sign_request_id='12345').get() print(f'Sign Request ID is {sign_request.id}') ``` ```cs .NET v6 theme={null} BoxSignRequest signRequest = await client.SignRequestsManager.GetSignRequestByIdAsync("12345"); ``` ```javascript Node v4 theme={null} const sr = await client.signRequests.getById({ sign_request_id: 12345, }); console.log( `Sign request id ${sr.id} contains ${sr.source_files.length} files` ); ``` # Multiple signers and signing order Source: https://developer.box.com/guides/box-sign/multiple-signers Assign signer, approver, and final copy reader roles, and control the order in which Box Sign notifies each person. A Box Sign request can include several people, each with a different job. Set a role for every person, and use `order` when they need to act one after another. Set both when you [create the request](/guides/box-sign/create-sign-request). ## Signer roles Each person needs a role: `signer`, `approver`, or `final_copy_reader`. | Role | What they do | | - | - | | `signer` | Adds data to the document: signature, initials, date, text, checkboxes, or radio buttons. | | `approver` | Approves the request before preparation (if enabled) and before any signer is notified. | | `final_copy_reader` | Does not sign. Receives a copy of the signed document and [signing log][log]. | If you do not give the requester a role, Box Sign adds them as `final_copy_reader`. Approvers and final copy readers cannot have signature properties. If you assign signature properties to them, Box Sign changes their role to `signer`. In the prepare UI, each signer has a color so you can see which fields belong to which person. For more detail on what each role can do, see the [roles for signers][roles] support article. ## Signing order If you omit `order`, Box Sign notifies every signer at the same time. If you set `order`, only the lowest number is notified first. After they sign, the next group is notified. Signers that share an `order` value receive the request at the same time. Box Sign adds a new file version to `parent_folder` as each signer completes. A decline stops the request for everyone who has not signed yet, and the request status becomes `declined`.
Multiple signer flow
```sh theme={null} curl -i -X POST "https://api.box.com/2.0/sign_requests" \ -H "authorization: Bearer " \ -H "content-type: application/json" \ -d '{ "is_document_preparation_needed": true, "parent_folder": { "type": "folder", "id": "0987654321" }, "source_files": [ { "type": "file", "id": "123456789" } ], "signers": [ { "email": "institution@example.com", "role": "signer", "order": 1 }, { "email": "student@example.com", "role": "signer", "order": 2 }, { "email": "dean@example.com", "role": "approver" }, { "email": "legal@example.com", "role": "final_copy_reader" } ] }' ``` Flow with an approver, two signers, and a final copy reader In this example, the dean approves first, then the institution signs, then the student signs, and legal receives the final copy. When you create the request from a template, keep signer order in the `POST` body the same as the order on the template so the correct person gets each field. See [Create a sign request with a template](/guides/box-sign/document-types/sign-templates). [roles]: https://support.box.com/hc/en-us/articles/4404105660947-Roles-for-signers [log]: https://support.box.com/hc/en-us/articles/4404095202579-Viewing-the-signing-log # Quick start Source: https://developer.box.com/guides/box-sign/quick-start Send your first Box Sign request with a file, destination folder, and signer email. This walkthrough creates a basic request. For every creation option, see [Create a Box Sign request](/guides/box-sign/create-sign-request). Imagine that you have a document stored in Box and you want to send it to a customer for signature. At a minimum your app needs to know what document to sign, where to store the signed document, and the signer email. ## Creating a signature request Use the [Box Sign API](/reference/post-sign-requests) or one of the available [SDKs](/guides/tooling/sdks) to create a signature request. See the example: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data-raw '{ "is_document_preparation_needed": true, "parent_folder": { "id": "234102987614", "type": "folder" }, "source_files": [ { "id": "1355143830404", "type": "file" } ], "signers": [ { "email": "signer@example.com", "role": "signer" } ] }' ``` ```python Python v10 theme={null} sign_request = client.sign_requests.create_sign_request( [SignRequestCreateSigner(email=signer_email)], source_files=[FileBase(id=document_id)], parent_folder=FolderMini(id=destination_folder_id), is_document_preparation_needed=True, ) if sign_request.prepare_url is not None: print(sign_request.prepare_url) ``` This results in a signature request with a prepare document URL (simplified): ```json cURL theme={null} { "is_document_preparation_needed": true, "signers": [ { "email": "requester@example.com", "role": "final_copy_reader", }, { "email": "signer@example.com", "role": "signer", } ], "id": "348decab-48a8-4f2c-9436-8967afebf7bb", "prepare_url": "https://app.box.com/sign/document/xyz-abc-123/.../prepare_doc/", "source_files": [ { "id": "1355143830404", "type": "file", } ], "parent_folder": { "id": "234102987614", "type": "folder", }, "name": "Simple-PDF.pdf", "type": "sign-request", "status": "converting", "sign_files": { "files": [ { "id": "1381301154812", "type": "file", } ], "is_ready_for_download": true }, "template_id": null } ``` ```yaml theme={null} Simple sign request with prep: xyz-abc-123 Status: converting Signers: signer@example.com Prepare url: https://app.box.com/sign/document/xyz-abc-123/.../prepare_doc/ ``` ## Check the status of the signature request Creating the signature request is an asynchronous process, and can generate errors. Your application needs to check the status of the request before proceeding, and handle any errors. A signature request can have the following statuses: Signature flow For the full status list, see [Box Sign request status](/guides/box-sign/request-status). ## Preparing the document In this example, we are signing a PDF, and the Box Sign engine has no idea where to place the signature pad field or any other inputs. This is why we used the `is_document_preparation_needed` flag. If a prepare URL is present, your application opens the prepare URL in a browser, where the requester can add the signature pad field and any other inputs needed for the signer to complete the document. Once the document is prepared, the requester can send the signature request to the signer. This preparation step is not always necessary. Take a look at [document types][technical-use-cases] for more information. ## Completing the signature request The signer then receives an email from Box with a link to the signature request. The signer can select the link and sign the document. When the process is completed, both a signature log containing metadata and the signed document are stored in the destination folder. Congratulations! You have successfully created your first signature request. This is the basic Box Sign create flow. For every creation option, see [Create a Box Sign request](/guides/box-sign/create-sign-request). For document types and other scenarios, see [Document types](/guides/box-sign/document-types). [technical-use-cases]: /guides/box-sign/document-types # Box Sign request status Source: https://developer.box.com/guides/box-sign/request-status What each Box Sign request status means, and which statuses let you resend or cancel a request. Creating a signature request is asynchronous, so check the status before you treat a request as sent. The status is returned when you [create](/guides/box-sign/create-sign-request), [get, or list](/guides/box-sign/list-sign-requests) a request. ## Status values * `converting`: The file is converted to a `.pdf` after you send the request. * `error_converting`: Conversion to `.pdf` failed. * `created`: `is_document_preparation_needed` is `true` and no one has visited the prepare URL yet. * `sent`: The request was sent and no signer has interacted with it. * `viewed`: The first signer selected **Review document** in the email or opened the signing URL. * `signed`: All signers completed the request. * `declined`: A signer declined the request. * `cancelled`: The request was cancelled in the UI or API. For more information, see [Cancel a Box Sign request](/guides/box-sign/cancel-sign-request). * `expired`: `days_valid` passed with incomplete signatures. For more information, see [Request expiration](/guides/box-sign/create-sign-request#request-expiration). An error status means you need a new sign request to retry.
Status diagram
The [Box Sign request resource](/reference/sign-requests-resources) lists the complete set of values the `status` field can return, including states that this guide does not describe individually. ## What you can do in each status | Action | Allowed when the status is | | - | - | | [Resend](/guides/box-sign/resend-sign-request) | The request is still open and was last sent more than 10 minutes ago | | [Cancel](/guides/box-sign/cancel-sign-request) | The request has not been signed or declined, and the document is not still converting | # Resend a Box Sign request Source: https://developer.box.com/guides/box-sign/resend-sign-request Resend a Box Sign request email to remaining signers, or enable automatic reminders when you create the request. You can send the signing email again to anyone who has not signed yet, or turn on automatic reminders when you create the request. ## Manual resend Use the [resend a Box Sign request](/reference/post-sign-requests-id-resend) endpoint to email remaining signers. You cannot resend a request when the [status](/guides/box-sign/request-status) is `signed`, `cancelled`, `declined`, `expired`, `error_sending`, or `error_converting`. Wait 10 minutes after the last send before you resend. A resend inside that window returns a `400 Bad Request` error. You can resend a request when the status is `created`, `sent`, `viewed`, or `error`. ```sh cURL theme={null} curl -i -X POST "https://api.box.com/2.0/sign_requests//resend" \ -H "authorization: Bearer " ``` ```java Java v5 theme={null} BoxSignRequest signRequest = new BoxSignRequest(api, id); BoxSignRequest.Info signRequestInfo = signRequest.getInfo(); signRequestInfo.resend(); ``` ```python Python v4 theme={null} sign_request = client.sign_request(sign_request_id='12345') sign_request.resend() ``` ```cs .NET v6 theme={null} await client.SignRequestsManager.ResendSignRequestAsync("12345"); ``` ```javascript Node v4 theme={null} const id = 12345; await client.signRequests.resendById({ sign_request_id: id }); console.log(`Sign request id ${sr.id} resent`); ``` ## Automatic reminders When you create a request, set `are_reminders_enabled` to `true`. Box Sign emails signers who have not signed yet after 3, 8, 13, and 18 days. ```sh theme={null} curl -i -X POST "https://api.box.com/2.0/sign_requests" \ -H "authorization: Bearer " \ -H "content-type: application/json" \ -d '{ "are_reminders_enabled": true, "parent_folder": { "type": "folder", "id": "0987654321" }, "source_files": [ { "type": "file", "id": "123456789" } ], "signers": [ { "email": "signer@example.com", "role": "signer" } ] }' ``` # Set up Box Sign Source: https://developer.box.com/guides/box-sign/setup Check your account type, enable the required scopes, and set up a safe place to test before you call the Box Sign API. Complete these steps once before you send your first signature request. When you are set up, go to the [quick start](/guides/box-sign/quick-start). ## Supported account types The following account types support requests through the Box Sign API: Business, Business Plus, Enterprise, Enterprise Suites, Enterprise Plus, and Enterprise Advanced. To locate your account type, navigate to **Account Settings** and scroll down to the **Account Details** section of the **Account** tab. For Admin details on restricting access, see the [support article][restrict]. ## Required scopes The following [scopes](/guides/api-calls/permissions-and-errors/scopes) must be enabled for an application before use of Box Sign's endpoints. * [Read all files and folders stored in Box](/guides/api-calls/permissions-and-errors/scopes#read-all-files-and-folders) * [Write all files and folders stored in Box](/guides/api-calls/permissions-and-errors/scopes#read-and-write-all-files-and-folders) * [Manage signature requests](/guides/api-calls/permissions-and-errors/scopes#manage-signature-requests) Depending on the selected authentication method and enterprise's settings, your application can require admin authorization or re-authorization before successful use of any newly selected scopes. To create webhooks for Sign events, your application also needs the **Manage Webhooks** scope. See [Box Sign webhooks](/guides/box-sign/webhooks). ## Rate limits See the [rate limit guide](/guides/api-calls/permissions-and-errors/rate-limits#per-api-rate-limits) for more information. ## Events Box Sign activity appears in the Box event stream. See the [events guide](/guides/events/event-triggers/sign-events) for more information. ## Testing Due to feature parity, familiarize yourself with [Box Sign functionality using the Box web app][webapp] before leveraging the API. As with all API endpoints, we recommend testing through the [developer sandbox environment][sandbox] to eliminate the risk of impacting production content. [restrict]: https://support.box.com/hc/en-us/articles/4404076971155-Enabling-Box-Sign [webapp]: https://support.box.com/hc/en-us/articles/4404105810195-Sending-a-document-for-signature [sandbox]: https://support.box.com/hc/en-us/articles/360043697274-Managing-developer-sandboxes-for-Box-admins # Verify signer identity Source: https://developer.box.com/guides/box-sign/signer-verification Require a signer to verify their identity with a smart card, SMS, or Box login, and add a password to a Box Sign request. You can require a signer to verify their identity with a CAC/PIV smart card, SMS, or Box login. You can also add a `password`. Set these on the signer when you [create the request](/guides/box-sign/create-sign-request), or on a template. Signer verification options on a signature request ## Verification methods Use `verification_method` on the signer when you create the request. This is the recommended approach for new integrations. | Type | Description | | - | - | | `cac_piv` | CAC/PIV smart card. Your enterprise must allow this method or the API returns `403 Forbidden`. Multiple signers who use this method must have a signing `order`. | | `phone` | SMS verification. Include `phone_number` with a country code prefixed by `+` or `00`, for example `+15551232190`. | | `login` | The signer must log in to a Box account. If they do not have an account, they can create a free one. | When you get a request or template, `verification_method` is `null` if that signer has no verification. If you set both `verification_method` and a legacy property such as `login_required` or `verification_phone_number`, `verification_method` takes precedence. ```sh theme={null} curl -i -X POST "https://api.box.com/2.0/sign_requests" \ -H "authorization: Bearer " \ -H "content-type: application/json" \ -d '{ "parent_folder": { "type": "folder", "id": "0987654321" }, "source_files": [ { "type": "file", "id": "123456789" } ], "signers": [ { "email": "signer@example.com", "role": "signer", "password": "example-password", "verification_method": { "type": "phone", "phone_number": "+15551232190" } } ] }' ``` ## Password protection You can add a `password` alongside any verification method. Password verification happens before the signer can open the request. Password prompt before the signer opens the request ## SMS verification (legacy) `verification_phone_number` is still supported. Prefer `verification_method` when you create the request. By default, SMS verification happens at the signing step, so the signer can view the request first. Set `is_phone_verification_required_to_view` to `true` to require SMS before view. SMS verification prompt Entering the SMS code For more background on signer authentication, see the [additional signer authentication][2fa] support article. [2fa]: https://support.box.com/hc/en-us/articles/4406861109907-Additional-Signer-Authentication # Box Sign webhooks Source: https://developer.box.com/guides/box-sign/webhooks Listen for Box Sign request events on a folder, create a webhook, and handle the notification payload. Signature requests are asynchronous. Signers can complete, decline, or ignore a request at any time, including outside your app. A webhook on the destination folder notifies your app when those events happen. ## Sign-related events Set the listener on a folder or a document. The usual approach is the folder where you create signature requests, so one webhook covers every request in that folder. Examples of events you can listen for: * `SIGN_REQUEST.COMPLETED`, when a signature request is completed * `SIGN_REQUEST.DECLINED`, when a signature request is declined * `SIGN_REQUEST.EXPIRED`, when a signature request expires * `SIGN_REQUEST.SIGNER_EMAIL_BOUNCED`, when a signer's email is bounced * `SIGN_REQUEST.SIGNER_SIGNED`, when a particular signer signs the request * `SIGN_REQUEST.SIGNATURE_REQUESTED`, when a signature is requested from a signer * `SIGN_REQUEST.ERROR_FINALIZING`, when the signature request could not be processed For the full list of triggers, see [webhook triggers](/guides/webhooks/triggers). ## Create a Sign webhook Sign webhooks use the standard Box webhooks infrastructure. To receive Box Sign events, call the [create webhook](/reference/post-webhooks) endpoint. Set the `target` to the folder where you create signature requests, and list the `SIGN_REQUEST.*` triggers you want to receive. This API requires the application to have the **Manage Webhooks** scope enabled. ```sh theme={null} curl -i -X POST "https://api.box.com/2.0/webhooks" \ -H "authorization: Bearer " \ -H "content-type: application/json" \ -d '{ "target": { "id": "12345", "type": "folder" }, "address": "https://example.com/sign-webhooks", "triggers": [ "SIGN_REQUEST.COMPLETED", "SIGN_REQUEST.DECLINED", "SIGN_REQUEST.EXPIRED" ] }' ``` For more ways to create webhooks, including the Developer Console and SDK examples, see [Create webhooks (v2)](/guides/webhooks/v2/create-v2). ## Receive Sign event notifications When a Box Sign event occurs, Box sends an HTTP `POST` request to the `address` you configured. The payload includes the trigger and Sign-specific `additional_info`, such as the sign request ID and signer emails. It can also include a `source` file for the signature request. For example, when a signature request is completed, Box sends a payload similar to the following: ```json theme={null} { "type": "webhook_event", "id": "eb0c4e06-751f-442c-86f8-fd5bb404dbec", "created_at": "2024-05-22T08:28:56-07:00", "trigger": "SIGN_REQUEST.COMPLETED", "webhook": { "id": "53", "type": "webhook" }, "created_by": { "type": "user", "id": "226067247", "name": "John Q. Developer", "login": "johnq@dev.name" }, "source": { "id": "73835521473", "type": "file", "name": "Contract.pdf", "item_status": "active" }, "additional_info": { "sign_request_id": "123e4567-e89b-12d3-a456-426614174000", "signer_emails": [ "johnq@dev.name", "signer@example.com" ], "external_id": "" } } ``` Your endpoint must respond with an HTTP status code in the `200` to `299` range within 30 seconds to acknowledge the notification. For the full payload format and delivery details, see [webhooks (v2)](/guides/webhooks/v2). To verify that a notification was sent by Box, see [Verify Box webhook signatures](/guides/webhooks/v2/signatures-v2). # Box Doc Split Source: https://developer.box.com/guides/docgen/doc-split Use the Box API to break a multi-page PDF stored in Box into smaller sub-documents and save the results to a Box folder. Box Doc Split breaks a multi-page PDF stored in Box into smaller sub-documents and saves the results to a Box folder. Use Box Doc Split when you need to: * Break a multi-page PDF into page-range segments you define yourself, using a manual split. * Let Box interpret natural language instructions to determine how pages are grouped, using a smart split. ## Example use cases | Team | Example | | - | - | | Sales | Split multi-deal packets into individual quotes | | Legal | Isolate signatures and ID proofs from contract packages | | Finance | Extract invoices from a multi-invoice PDF | | Operations | Split scan batches at QR code separators | | HR | Separate onboarding packet sections | ## Prerequisites Before you start, follow the steps in the get started with Box Doc Gen guide to create a platform app and generate an access token. You also need: * A source PDF already stored in Box. * A destination folder in Box that your app can write to. ## API version All requests to Box Doc Split endpoints must specify a valid API version by setting the `box-version` header to `2026.0`. For more details, see Box API versioning. ## Split types The `split_type` you send determines the rest of the request body. | Split type | Best for | You provide | | - | - | - | | `manual` | Known page ranges | A `split_definition` array of start and end pages | | `smart` | Content-based or marker-based boundaries | A natural language `prompt` | Use `manual` when the page numbers are known, because the results are deterministic. Use `smart` when the boundaries are easier to describe than to calculate. A smart split consumes AI Units. Box consumes 1 AI Unit for every 4 pages processed. ## How Box Doc Split works Box Doc Split runs asynchronously in two steps: 1. Send a `POST` request to `/2.0/document_splits` to create a split job. The response contains a job ID. 2. Send a `GET` request to `/2.0/document_splits/{document_split_id}` with that ID to retrieve the status and the generated files. ### Create a split job The request body has the same shape for both split types. Only the contents of `split_input` differ. | Parameter | Description | | - | - | | `source_file_id` | The Box file ID of the PDF to split | | `destination_folder_id` | The Box folder ID where Box stores the output documents | | `input_source` | The source of the input. Use `api` for all API-based requests | | `split_input.split_type` | Either `manual` or `smart` | | `split_input.split_definition` | The page ranges for a manual split | | `split_input.prompt` | The natural language prompt for a smart split | For a complete request example, see the manual split or smart split guide. The response contains the ID of the split job: ```json theme={null} { "id": "11678", "type": "document_split" } ``` ### Get the status and results of a split job Use the job ID from the `POST` response to retrieve the details of the split. ```sh cURL theme={null} curl -L 'https://api.box.com/2.0/document_splits/128445' \ -H 'box-version: 2026.0' \ -H 'Authorization: Bearer ' ``` The response lists the generated files, the split definition Box applied, the destination folder, and other details of the request. ```json theme={null} { "output": { "generated_files": [ { "id": "2404332574822", "type": "file" }, { "id": "2404326035290", "type": "file" } ], "split_definition": [ { "start": 1, "end": 5 }, { "start": 7, "end": 9 } ] }, "destination_folder": { "id": "408521968626", "type": "folder" }, "created_by": { "id": "36302517035", "type": "user" }, "enterprise": { "id": "1217301449", "type": "enterprise" } } ``` For a smart split, the `split_definition` in the response shows the ranges Box derived from your prompt. Use it to confirm that the prompt produced the grouping you expected. # Get started with Box Doc Gen Source: https://developer.box.com/guides/docgen/docgen-getting-started To start generating documents with Box Doc Gen API you will need a platform application and a developer token to authenticate your calls. You also need a Doc Gen template that will serve as an input source for your document. ## Enable Box Doc Gen To use Box Doc Gen, make sure it is enabled by an admin in the Admin Console. If you are a Box Admin, you will find the necessary information in [Enterprise Settings: Content & Sharing Tab][settings] documentation. ## Create and upload a Box Doc Gen template To use Box Doc Gen API to generate documents, a Box Doc Gen template must already exist in Box. You have the following options to create a template: * Install the [Box Doc Gen Template Creator add-in for Microsoft Word][template-addin]. * Create a Box Doc Gen template [using a JSON file][json-template] or manually create [template tags][template-tags]. ## Create a platform application First you need to create a platform application you will use to make calls. To create an application, follow the guide on creating platform apps. ## Generate a developer token You need a developer token to authenticate your app when sending requests. To generate a token: 1. Go to **Developer Console** > **My Platform Apps**. 2. Click the **Options menu** button (…) on the right. 3. Select **Generate Developer Token**. The token will be automatically generated and saved to clipboard. generate token You can also open your app, go to **Configuration** > **Developer Token** and generate the token. A developer token is only valid for one hour. For additional details, see developer token. After you generate the token, you can use it in cURL or other clients, such as Postman, to make calls. ## Use webhooks You can create webhooks to monitor Doc Gen events and automate your business process or workflow. Follow the instructions for adding webhooks. Your content type is your Doc Gen template file or folder. The supported events are: * `DOCGEN_DOCUMENT_GENERATION_STARTED` * `DOCGEN_DOCUMENT_GENERATION_SUCCEEDED` * `DOCGEN_DOCUMENT_GENERATION_FAILED` Doc Gen event triggers Information that is posted in a notification: * Trigger name. * Webhook trigger timestamp. * Template file ID. * Template file version ID. * Template file name. * Destination folder. * Generated file ID (if the document generation process succeeds). * Output type (DOCX or PDF). * Reason (if the document generation process fails). [settings]: https://support.box.com/hc/en-us/articles/4404822772755-Enterprise-Settings-Content-Sharing-Tab#h_01FYQGK5RW42T07GV985MQ9E9A [template-addin]: https://support.box.com/hc/en-us/articles/36587535449747-Installing-Box-Doc-Gen-Add-in [template-tags]: https://support.box.com/hc/en-us/articles/36151895655059-Creating-A-Box-Doc-Gen-Template-Manually [json-template]: https://support.box.com/hc/en-us/articles/36148012877843-Creating-a-Box-Doc-Gen-Template-using-JSON-data # Box Doc Gen jobs Source: https://developer.box.com/guides/docgen/docgen-jobs A Box Doc Gen job runs when you make a request to generate a document. The `document_generation_data` parameter in the `POST` request is an array of entries that represent Box Doc Gen jobs run to generate a document. ```sh cURL theme={null} curl -L 'https://api.box.com/2.0/docgen_batches' \ -H 'box-version: 2025.0' \ -H 'Authorization: Bearer ' \ -D '{ "file": { "id": "12345678", "type": "file" }, "input_source": "api", "destination_folder": { "id": "12345678", "type": "folder" }, "output_type": "docx", "document_generation_data": [ { "generated_file_name": "Image test", "user_input": { "order": { "id": "12305", "date": "18-08-2023", "country": "US", "expiryDate": "18-08-2024", "currency": "$", "amount": 5060.5, "taxRate": 10, "requester": "John", "approver": "Smith", "department": "Procurement", "paymentTerms": "30 days", "deliveryTerms": "30 days", "deliveryDate": "18-09-2023", "vendor": { "company": "Example company", "address": { "street": "Example street", "city": "Example city", "zip": "EX-456" } }, "products": [ { "id": 1, "name": "A4 Papers", "type": "non-fragile", "quantity": 100, "price": 29, "amount": 2900 }, { "id": 2, "name": "Ink Cartridge", "type": "non-fragile", "quantity": 40, "price": 39, "amount": 1560 }, { "id": 3, "name": "Adhesive tape", "type": "non-fragile", "quantity": 20, "price": 30, "amount": 600.5 } ] } } } ]` ``` ```typescript Node/TypeScript v10 theme={null} await client.docgen.createDocgenBatchV2025R0({ file: new FileReferenceV2025R0({ id: uploadedFile.id }), inputSource: 'api', destinationFolder: new DocGenBatchCreateRequestV2025R0DestinationFolderField({ id: folder.id, }), outputType: 'pdf', documentGenerationData: [ { generatedFileName: 'test', userInput: { ['abc']: 'xyz' }, } satisfies DocGenDocumentGenerationDataV2025R0, ], } satisfies DocGenBatchCreateRequestV2025R0); ``` ```python Python v10 theme={null} client.docgen.create_docgen_batch_v2025_r0( FileReferenceV2025R0(id=uploaded_file.id), "api", CreateDocgenBatchV2025R0DestinationFolder(id=folder.id), "pdf", [ DocGenDocumentGenerationDataV2025R0( generated_file_name="test", user_input={"abc": "xyz"} ) ], ) ``` ```cs .NET v10 theme={null} await client.Docgen.CreateDocgenBatchV2025R0Async(requestBody: new DocGenBatchCreateRequestV2025R0(file: new FileReferenceV2025R0(id: uploadedFile.Id), inputSource: "api", destinationFolder: new DocGenBatchCreateRequestV2025R0DestinationFolderField(id: folder.Id), outputType: "pdf", documentGenerationData: Array.AsReadOnly(new [] {new DocGenDocumentGenerationDataV2025R0(generatedFileName: "test", userInput: new Dictionary() { { "abc", "xyz" } })}))); ``` ```swift Swift v10 theme={null} try await client.docgen.createDocgenBatchV2025R0(requestBody: DocGenBatchCreateRequestV2025R0(file: FileReferenceV2025R0(id: uploadedFile.id), inputSource: "api", destinationFolder: DocGenBatchCreateRequestV2025R0DestinationFolderField(id: folder.id), outputType: "pdf", documentGenerationData: [DocGenDocumentGenerationDataV2025R0(generatedFileName: "test", userInput: ["abc": "xyz"])])) ``` ```java Java v10 theme={null} client.getDocgen().createDocgenBatchV2025R0(new DocGenBatchCreateRequestV2025R0(new FileReferenceV2025R0(uploadedFile.getId()), "api", new DocGenBatchCreateRequestV2025R0DestinationFolderField(folder.getId()), "pdf", Arrays.asList(new DocGenDocumentGenerationDataV2025R0("test", mapOf(entryOf("abc", "xyz")))))) ``` Box Doc Gen API allows you to get information about the Box Doc Gen jobs. ## Prerequisites Before you start using Box Doc Gen API, follow the steps listed in the get started with Box Doc Gen guide to create a platform app and a Box Doc Gen template. ## List all Box Doc Gen jobs To get a list of all Box Doc Gen jobs that were run, use the `GET /2.0/docgen_jobs` endpoint. You don't have to provide any additional parameters. ```sh cURL theme={null} curl -i -X GET "https://api.box.com/2.0/docgen_jobs" \ -H 'box-version: 2025.0' \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.docgen.getDocgenJobsV2025R0({ limit: 10000, } satisfies GetDocgenJobsV2025R0QueryParams); ``` ```python Python v10 theme={null} client.docgen.get_docgen_jobs_v2025_r0(limit=10000) ``` ```cs .NET v10 theme={null} await client.Docgen.GetDocgenJobsV2025R0Async(queryParams: new GetDocgenJobsV2025R0QueryParams() { Limit = 10000 }); ``` ```swift Swift v10 theme={null} try await client.docgen.getDocgenJobsV2025R0(queryParams: GetDocgenJobsV2025R0QueryParams(limit: Int64(10000))) ``` ```java Java v10 theme={null} client.getDocgen().getDocgenJobsV2025R0(new GetDocgenJobsV2025R0QueryParams.Builder().limit(10000L).build()) ``` ## Get a Box Doc Gen job by ID To get a specific Box Doc Gen job, use the `GET /2.0/docgen_jobs_id` endpoint and provide the `job_id`. ```sh cURL theme={null} curl -i -X GET "https://api.box.com/2.0/docgen_jobs/12345" \ -H 'box-version: 2025.0' \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.docgen.getDocgenJobByIdV2025R0(docgenJobItemFromList.id); ``` ```python Python v10 theme={null} client.docgen.get_docgen_job_by_id_v2025_r0(docgen_job_item_from_list.id) ``` ```cs .NET v10 theme={null} await client.Docgen.GetDocgenJobByIdV2025R0Async(jobId: docgenJobItemFromList.Id); ``` ```swift Swift v10 theme={null} try await client.docgen.getDocgenJobByIdV2025R0(jobId: docgenJobItemFromList.id) ``` ```java Java v10 theme={null} client.getDocgen().getDocgenJobByIdV2025R0(docgenJobItemFromList.getId()) ``` ## Get Box Doc Gen jobs in batch with a specific ID A single request can generate several documents. In such a case, a separate generation job is run for each document and all these jobs are included in one "batch" meaning a request. To get all jobs performed within one request, use the `GET /2.0/docgen_batch_jobs_id` endpoint and provide the `batch_id`. ```sh cURL theme={null} curl -i -X GET "https://api.box.com/2.0/docgen_jobs/12345" \ -H 'box-version: 2025.0' \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.docgen.getDocgenJobByIdV2025R0(docgenJobItemFromList.id); ``` ```python Python v10 theme={null} client.docgen.get_docgen_job_by_id_v2025_r0(docgen_job_item_from_list.id) ``` ```cs .NET v10 theme={null} await client.Docgen.GetDocgenJobByIdV2025R0Async(jobId: docgenJobItemFromList.Id); ``` ```swift Swift v10 theme={null} try await client.docgen.getDocgenJobByIdV2025R0(jobId: docgenJobItemFromList.id) ``` ```java Java v10 theme={null} client.getDocgen().getDocgenJobByIdV2025R0(docgenJobItemFromList.getId()) ``` # Box Doc Gen templates Source: https://developer.box.com/guides/docgen/docgen-templates Box Doc Gen API allows you to retrieve information related to Box Doc Gen templates. ## Prerequisites Before you start using Box Doc Gen API, follow the steps listed in the get started with Box Doc Gen guide to create a platform app and a Box Doc Gen template. ## List Box Doc Gen templates To get a list of all created Box Doc Gen templates, use the `GET /2.0/docgen_templates` endpoint. You don't have to provide any additional parameters. ```sh cURL theme={null} curl -L 'https://api.box.com/2.0/docgen_templates' \ -H 'box-version: 2025.0' \ -H 'Authorization: Bearer ' ``` ```typescript Node/TypeScript v10 theme={null} await client.docgenTemplate.getDocgenTemplatesV2025R0(); ``` ```python Python v10 theme={null} client.docgen_template.get_docgen_templates_v2025_r0() ``` ```cs .NET v10 theme={null} await client.DocgenTemplate.GetDocgenTemplatesV2025R0Async(); ``` ```swift Swift v10 theme={null} try await client.docgenTemplate.getDocgenTemplatesV2025R0() ``` ```java Java v10 theme={null} client.getDocgenTemplate().getDocgenTemplatesV2025R0() ``` The response will contain an `entries` array listing the already created Box Doc Gen templates. ## Get a Box Doc Gen template by ID To get a specific Box Doc Gen template, use the `GET /2.0/docgen_templates_id` endpoint and provide the `template_id`. ```sh cURL theme={null} curl -L 'https://api.box.com/2.0/docgen_templates/12345678' \ -H 'box-version: 2025.0' \ -H 'Authorization: Bearer ' ``` ```typescript Node/TypeScript v10 theme={null} await client.docgenTemplate.getDocgenTemplateByIdV2025R0( createdDocgenTemplate.file!.id, ); ``` ```python Python v10 theme={null} client.docgen_template.get_docgen_template_by_id_v2025_r0( created_docgen_template.file.id ) ``` ```cs .NET v10 theme={null} await client.DocgenTemplate.GetDocgenTemplateByIdV2025R0Async(templateId: NullableUtils.Unwrap(createdDocgenTemplate.File).Id); ``` ```swift Swift v10 theme={null} try await client.docgenTemplate.getDocgenTemplateByIdV2025R0(templateId: createdDocgenTemplate.file!.id) ``` ```java Java v10 theme={null} client.getDocgenTemplate().getDocgenTemplateByIdV2025R0(createdDocgenTemplate.getFile().getId()) ``` The response will contain details of a file that was used as a Box Doc Gen template. ## List all document generation jobs for a template To get a list of all created Box Doc Gen templates, use the `GET /2.0/docgen_template_jobs_id` endpoint and provide the `template_id`. ```sh cURL theme={null} curl -L 'https://api.box.com/2.0/docgen_template_jobs/12345678' \ -H 'box-version: 2025.0' \ -H 'Authorization: Bearer ' ``` ```typescript Node/TypeScript v10 theme={null} await client.docgenTemplate.getDocgenTemplateJobByIdV2025R0( fetchedDocgenTemplate.file!.id, ); ``` ```python Python v10 theme={null} client.docgen_template.get_docgen_template_job_by_id_v2025_r0( fetched_docgen_template.file.id ) ``` ```cs .NET v10 theme={null} await client.DocgenTemplate.GetDocgenTemplateJobByIdV2025R0Async(templateId: NullableUtils.Unwrap(fetchedDocgenTemplate.File).Id); ``` ```swift Swift v10 theme={null} try await client.docgenTemplate.getDocgenTemplateJobByIdV2025R0(templateId: fetchedDocgenTemplate.file!.id) ``` ```java Java v10 theme={null} client.getDocgenTemplate().getDocgenTemplateJobByIdV2025R0(fetchedDocgenTemplate.getFile().getId()) ``` The response will contain a list of Box Doc Gen jobs that were run to generate documents. # Generate documents Source: https://developer.box.com/guides/docgen/generate-document The `POST /2.0/docgen_batches` endpoint allows you to generate a document using Box Doc Gen template as input. ## Prerequisites Before you start using Box Doc Gen API, follow the steps listed in the get started with Box Doc Gen guide to create a platform app and a Box Doc Gen template. ## Send a request To generate a document or a set of documents, use the `POST /2.0/docgen_batches` endpoint. ### Parameters To make a call, you need to pass the following parameters. Mandatory parameters are in **bold**. | Parameter | Description | Example | | - | - | - | | **`file.id`** | ID of the file to be marked as Box Doc Gen template. | `12345678` | | **`file.type`** | The type of provided input. The value is always **`file`**. | `file` | | `file_version` | The file version of a template. | `12345` | | **`input_source`** | The input source for generated document. The value has to be `api` for all the API-based document generation requests. | `api` | | **`output_type`** | The output file type. | `docx`, `pdf` | | **`destination_folder.id`** | The ID of the folder where the generated document will be stored. | `12345678` | | **`destination_folder.type`** | The type of the destination item. Since the generated files are stored in folders, the value is always **`folder`**. | `file` | | **`document_generation_data.generated_file_name`** | The name of the generated file. | `New_Template` | | **`document_generation_data.user_input`** | The JSON data to be used to generate document. | `{"id": 2, "name": "Ink Cartridge", "type": "non-fragile"}` | ## Use case When your Box Doc Gen template and JSON data is ready, you can make a request to Box Doc Gen API to generate documents. A sample call looks as follows: ```sh cURL theme={null} curl -L 'https://api.box.com/2.0/docgen_batches' \ -H 'box-version: 2025.0' \ -H 'Authorization: Bearer ' \ -D '{ "file": { "id": "12345678", "type": "file" }, "input_source": "api", "destination_folder": { "id": "12345678", "type": "folder" }, "output_type": "docx", "document_generation_data": [ { "generated_file_name": "Image test", "user_input": { "order": { "id": "12305", "date": "18-08-2023", "country": "US", "expiryDate": "18-08-2024", "currency": "$", "amount": 5060.5, "taxRate": 10, "requester": "John", "approver": "Smith", "department": "Procurement", "paymentTerms": "30 days", "deliveryTerms": "30 days", "deliveryDate": "18-09-2023", "vendor": { "company": "Example company", "address": { "street": "Example street", "city": "Example city", "zip": "EX-456" } }, "products": [ { "id": 1, "name": "A4 Papers", "type": "non-fragile", "quantity": 100, "price": 29, "amount": 2900 }, { "id": 2, "name": "Ink Cartridge", "type": "non-fragile", "quantity": 40, "price": 39, "amount": 1560 }, { "id": 3, "name": "Adhesive tape", "type": "non-fragile", "quantity": 20, "price": 30, "amount": 600.5 } ] } } } ]` ``` ```typescript Node/TypeScript v10 theme={null} await client.docgen.createDocgenBatchV2025R0({ file: new FileReferenceV2025R0({ id: uploadedFile.id }), inputSource: 'api', destinationFolder: new DocGenBatchCreateRequestV2025R0DestinationFolderField({ id: folder.id, }), outputType: 'pdf', documentGenerationData: [ { generatedFileName: 'test', userInput: { ['abc']: 'xyz' }, } satisfies DocGenDocumentGenerationDataV2025R0, ], } satisfies DocGenBatchCreateRequestV2025R0); ``` ```python Python v10 theme={null} client.docgen.create_docgen_batch_v2025_r0( FileReferenceV2025R0(id=uploaded_file.id), "api", CreateDocgenBatchV2025R0DestinationFolder(id=folder.id), "pdf", [ DocGenDocumentGenerationDataV2025R0( generated_file_name="test", user_input={"abc": "xyz"} ) ], ) ``` ```cs .NET v10 theme={null} await client.Docgen.CreateDocgenBatchV2025R0Async(requestBody: new DocGenBatchCreateRequestV2025R0(file: new FileReferenceV2025R0(id: uploadedFile.Id), inputSource: "api", destinationFolder: new DocGenBatchCreateRequestV2025R0DestinationFolderField(id: folder.Id), outputType: "pdf", documentGenerationData: Array.AsReadOnly(new [] {new DocGenDocumentGenerationDataV2025R0(generatedFileName: "test", userInput: new Dictionary() { { "abc", "xyz" } })}))); ``` ```swift Swift v10 theme={null} try await client.docgen.createDocgenBatchV2025R0(requestBody: DocGenBatchCreateRequestV2025R0(file: FileReferenceV2025R0(id: uploadedFile.id), inputSource: "api", destinationFolder: DocGenBatchCreateRequestV2025R0DestinationFolderField(id: folder.id), outputType: "pdf", documentGenerationData: [DocGenDocumentGenerationDataV2025R0(generatedFileName: "test", userInput: ["abc": "xyz"])])) ``` ```java Java v10 theme={null} client.getDocgen().createDocgenBatchV2025R0(new DocGenBatchCreateRequestV2025R0(new FileReferenceV2025R0(uploadedFile.getId()), "api", new DocGenBatchCreateRequestV2025R0DestinationFolderField(folder.getId()), "pdf", Arrays.asList(new DocGenDocumentGenerationDataV2025R0("test", mapOf(entryOf("abc", "xyz")))))) ``` When the request is being processed, each entry in the `document_generation_data` array is treated as a separate document generation job that Box Doc Gen adds to the document generation queue. Generated documents will be saved in the designated folder. # Box Doc Gen Source: https://developer.box.com/guides/docgen/index Box Doc Gen API is available only for Enterprise Advanced accounts. Box Doc Gen allows you to generate business documents such as offer letters, sales contracts, invoices or agreements. You can generate documents based on Box Doc Gen templates uploaded to Box, with data fields that can be dynamically filled using Box Doc Gen API. Box Doc Gen only supports the ability to leverage English template tags when using Box Doc Gen templates. We recommend that customers test and review that Box Doc Gen supports their desired language requirements. ## Prerequisites To use Box Doc Gen, you must have access to Microsoft Word, as it is required for creating and authoring your document templates. You can utilize the Box Doc Gen Add-in for a code-free experience or apply tagging scripts within Word to prepare your documents. Box Doc Gen is designed to facilitate the dynamic generation of business documents, but it is important to note that Box does not have control over users’ access to Microsoft Word. Users must ensure they have the necessary permissions and access to Microsoft Word to create and author document templates effectively. ## Box Doc Gen API capabilities Box Doc Gen API allows you to: * mark documents as Box Doc Gen templates, * generate documents based on Box Doc Gen templates you store in Box, * examine the details of Box Doc Gen templates and document generation jobs. ## Box Doc Gen API version Box Doc Gen API was released in Box API version `2025.0`. All API requests to Box Doc Gen API endpoints must specify a valid API version by setting the `box-version` header to `2025.0`. For more details, see Box API versioning. ## Box Doc Gen workflow A flow diagram representing Box Doc Gen workflow 1. Author your Doc Gen Template * Use [Doc Gen Add-in for Microsoft Word][template-addin] to create a template without any code. * You can also leverage [Doc Gen's tagging script][tagging-script] to author the template. 2. [Add the template to Box][upload-template] using the Box Doc Gen UI. At this point, you can: * Mark an existing file in Box as Doc Gen template. * Create or Upload a document and mark it as a Box Doc Gen template. 3. Generate the document using Box Doc Gen API. [template-addin]: https://support.box.com/hc/en-us/articles/36587535449747-Installing-Box-Doc-Gen-Add-in [template-tags]: https://support.box.com/hc/en-us/articles/36151895655059-Creating-A-Box-Doc-Gen-Template-Manually [json-template]: https://support.box.com/hc/en-us/articles/36148012877843-Creating-a-Box-Doc-Gen-Template-using-JSON-data [tagging-script]: https://support.box.com/hc/en-us/articles/36149723736723-Template-tags-reference [upload-template]: https://support.box.com/hc/en-us/articles/36587432368275-Managing-Box-Doc-Gen-Templates-in-Relay # Manual split Source: https://developer.box.com/guides/docgen/manual-split Divide a PDF stored in Box into sub-documents using the exact page ranges you define. A manual split divides a PDF into sub-documents using the exact page ranges you define. Use this split type when you already know which pages belong together, such as a standard packet layout, a file an operator has reviewed, or page ranges supplied by an upstream system. ## Prerequisites Before you start, follow the steps in the get started with Box Doc Gen guide to create a platform app and generate an access token. You also need: * A source PDF already stored in Box. * A destination folder in Box that your app can write to. * The page ranges for each output document. If the boundaries depend on content or markers rather than fixed page numbers, use a smart split instead. ## What you configure | Parameter | Description | | - | - | | `source_file_id` | The Box file ID of the PDF to split | | `destination_folder_id` | The Box folder ID where Box stores the output documents | | `split_input.split_type` | `manual` | | `split_input.split_definition` | One or more page ranges, each with a `start` and an `end` | Each `{ start, end }` pair creates one output document. Page numbers are 1-based and ranges are inclusive. ## Example You have a 14-page sales packet and want two files, one for the order summary on pages 1 to 5 and one for the exhibits and terms on pages 6 to 14. The corresponding `split_definition` is: ```json theme={null} [ { "start": 1, "end": 5 }, { "start": 6, "end": 14 } ] ``` ## Create a manual split job ```sh cURL theme={null} curl -L 'https://api.box.com/2.0/document_splits' \ -H 'Accept: application/json; version=1' \ -H 'box-version: 2026.0' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "source_file_id": "21040505661", "destination_folder_id": "10720019396", "input_source": "api", "split_input": { "split_type": "manual", "split_definition": [ { "start": 1, "end": 5 }, { "start": 7, "end": 9 } ] } }' ``` The response contains the ID of the split job: ```json theme={null} { "id": "11678", "type": "document_split" } ``` Use this ID to retrieve the status and the generated files. ## Best practices * Use page ranges that do not overlap, so no page appears in more than one output document. * Order your ranges by page number to match the reading order of the source PDF. * Choose a manual split whenever the page numbers are already known, because the results are deterministic. * Give destination folders meaningful names, such as `Splits / Customer A / 2026-07`, so reviewers can find the output quickly. ## When to use a smart split instead Choose a smart split when: * Break points are marked by QR codes or separator pages. * You need to keep only certain types of content, such as signatures and ID proofs. * Page ranges vary from file to file and are easier to describe than to calculate. # Mark file as Box Doc Gen template Source: https://developer.box.com/guides/docgen/mark-template You can mark an existing document as a Box Doc Gen template and use it to generate documents. ## Before you start Before you start using Box Doc Gen API, follow the steps listed in the get started with Box Doc Gen guide to create a platform app and a Box Doc Gen template. ## Send a request To send a request containing your question, use the `POST /2.0/docgen_templates` endpoint and provide the mandatory parameters. ### Parameters To make a call you need to pass the following parameters. Mandatory parameters are in **bold**. | Parameter | Description | Example | | - | - | - | | **`file.id`** | ID of the file to be marked as the Box Doc Gen template. | `12345678` | | **`file.type`** | The type of provided input. The value is always **`file`**. | `file` | ## Use cases ### Mark a file as Box Doc Gen template The following sample shows you how to mark a file to ensure it is recognized as a Box Doc Gen template. The file must be in `.docx` format. ```sh cURL theme={null} curl -L 'https://api.box.com/2.0/docgen_templates' \ -H 'box-version: 2025.0' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -D '{ "file": { "id": "12345678", "type": "file" } }' ``` ```typescript Node/TypeScript v10 theme={null} await client.docgenTemplate.createDocgenTemplateV2025R0({ file: new FileReferenceV2025R0({ id: file.id }), } satisfies DocGenTemplateCreateRequestV2025R0); ``` ```python Python v10 theme={null} client.docgen_template.create_docgen_template_v2025_r0(FileReferenceV2025R0(id=file.id)) ``` ```cs .NET v10 theme={null} await client.DocgenTemplate.CreateDocgenTemplateV2025R0Async(requestBody: new DocGenTemplateCreateRequestV2025R0(file: new FileReferenceV2025R0(id: file.Id))); ``` ```swift Swift v10 theme={null} try await client.docgenTemplate.createDocgenTemplateV2025R0(requestBody: DocGenTemplateCreateRequestV2025R0(file: FileReferenceV2025R0(id: file.id))) ``` ```java Java v10 theme={null} client.getDocgenTemplate().createDocgenTemplateV2025R0(new DocGenTemplateCreateRequestV2025R0(new FileReferenceV2025R0(file.getId()))) ``` ### Remove Box Doc Gen template marking from a file To make sure a file is no longer marked as a Box Doc Gen template, use the `DELETE 2.0/docgen_templates/:template_id` request. ```sh cURL theme={null} curl -L -X DELETE 'https://api.box.com/2.0/docgen_templates/12345678' \ -H 'box-version: 2025.0' \ -H 'Authorization: Bearer ' ``` ```typescript Node/TypeScript v10 theme={null} await client.docgenTemplate.deleteDocgenTemplateByIdV2025R0( createdDocgenTemplate.file!.id, ); ``` ```python Python v10 theme={null} client.docgen_template.delete_docgen_template_by_id_v2025_r0( created_docgen_template.file.id ) ``` ```cs .NET v10 theme={null} await client.DocgenTemplate.DeleteDocgenTemplateByIdV2025R0Async(templateId: NullableUtils.Unwrap(createdDocgenTemplate.File).Id); ``` ```swift Swift v10 theme={null} try await client.docgenTemplate.deleteDocgenTemplateByIdV2025R0(templateId: createdDocgenTemplate.file!.id) ``` ```java Java v10 theme={null} client.getDocgenTemplate().deleteDocgenTemplateByIdV2025R0(createdDocgenTemplate.getFile().getId()) ``` # Smart split Source: https://developer.box.com/guides/docgen/smart-split Use a natural language prompt to let Box decide how to group the pages of a PDF into output documents. When `split_type` is `smart`, Box Doc Split uses your `prompt` to decide how to group and split pages into output documents. Clear, specific prompts produce more reliable splits. A smart split consumes AI Units. Box consumes 1 AI Unit for every 4 pages processed. ## Prerequisites Before you start, follow the steps in the get started with Box Doc Gen guide to create a platform app and generate an access token. You also need: * A source PDF already stored in Box. * A destination folder in Box that your app can write to. * A prompt that describes how to group the pages. ## What you configure | Parameter | Description | | - | - | | `source_file_id` | The Box file ID of the PDF to split | | `destination_folder_id` | The Box folder ID where Box stores the output documents | | `split_input.split_type` | `smart` | | `split_input.prompt` | A natural language prompt that tells Box how to analyze the source PDF and identify the splits | ## Prompt guidelines * State that the Box file you supply is the source document. * Describe the splitting rule, such as a page count, a marker like a QR code, or a content criterion. * Prefer sequential ranges when order matters, such as contract exhibits followed by signature pages. * Call out the pages or sections to include, and where it helps, the ones to skip. * Avoid ambiguous language such as "split somehow" or "use whatever makes sense." ## Example prompts ### Explicit page groups Split a file into two documents whose ranges you describe in natural language. ```text theme={null} Use this document as the source, and split the documents into 2 parts. Pages 1-3 should be the first document, and pages 5-8 should be the second document. ``` This produces two outputs, corresponding to pages 1 to 3 and pages 5 to 8. If you already know the exact ranges, use `split_type: "manual"` with a `split_definition` instead. See manual split for deterministic results. ### QR code as a break point Treat each QR code page as a separator between documents. Pages before a QR code form one split, and the QR code page itself is excluded unless you specify otherwise. ```text theme={null} Use this document as the source. Identify individual pages that contain a QR code, and use each QR code page as a breaking point between document splits. Build the split definition so that if a QR code appears on page 5, pages 1-4 are the first document. If the next QR code appears on page 11, pages 6-10 are the second document. Continue this pattern through the rest of the file. ``` This produces sequential ranges bounded by the marker pages. With markers on pages 5 and 11, the outputs cover pages 1 to 4 and pages 6 to 10. ### Content-based contractual pages Keep only the pages with substantive contractual content, such as ID proofs, signatures, and executed terms, and skip the boilerplate terms and conditions. Split the retained sections sequentially. ```text theme={null} Use this document as the source. Identify all pages that contain real contractual details, such as ID proofs of the signers, signatures of the signers, and other executed contractual details. Skip pages that only contain terms and conditions. Split the retained sections sequentially. For example, if ID proofs are on pages 1-3 and executed signatures are on pages 4-6, produce two documents: pages 1-3 and pages 4-6. ``` This produces one output for each contiguous block of content that matches the criteria, such as pages 1 to 3 and pages 4 to 6. ## Create a smart split job ```sh cURL theme={null} curl -L 'https://api.box.com/2.0/document_splits' \ -H 'Accept: application/json; version=1' \ -H 'box-version: 2026.0' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "source_file_id": "2390226613215", "destination_folder_id": "406457672606", "input_source": "api", "split_input": { "split_type": "smart", "prompt": "Identify pages with QR codes in the source file, use each one as a breaking point, and split the document. For example, if there is a QR code on page 5, followed by a QR code on page 11, followed by a QR code on page 18, the result is 2 split documents. The first covers pages 5 to 11 and the second covers pages 12 to 18." } }' ``` The response contains the ID of the split job: ```json theme={null} { "id": "11678", "type": "document_split" } ``` Use this ID to retrieve the status and the generated files. The `split_definition` in that response shows the ranges Box derived from your prompt, so you can confirm the prompt produced the grouping you expected. # Intelligent workflow Source: https://developer.box.com/guides/intelligent-workflow/index Build governed, agentic content workflows without running your own automation infrastructure. Box is built for content-centric intelligent workflows. Box Automate orchestrates triggers, AI agents, people, and document steps on files and folders that already inherit Box permissions, collaboration, retention, and audit. Box Doc Gen produces business documents from templates and writes them back into that same governed content layer. Learn how to design work around the documents people already use, instead of copying content into a separate automation or AI system. [Box Automate](https://developer.box.com/guides/box-automate/index) and [Box Doc Gen](https://developer.box.com/guides/docgen) are not included in the Free Developer Plan. ## Why Box for intelligent workflows Choose Box when the workflow's source of truth is **enterprise content** and you need automation, AI, and document production without leaving that content layer. * **Content stays governed** — Workflows operate on Box files and folders. Access tokens and collaborations still apply; you do not bypass waterfall permissions by using the API. See the security overview. * **Event-driven and human-driven triggers** — Box Automate workflows can start from file/folder events, metadata, tasks, File Request, Box Sign, forms, manual start, or HTTPS. * **AI as a workflow step** — Use Box AI in Automate to extract metadata, summarize content, or answer questions, then route the workflow based on that result. * **Document production** — Box Doc Gen fills Microsoft Word templates from API JSON (or Box Automate outcomes) and writes generated `docx` or `pdf` files back into Box. * **Cross-product handoffs** — One Box Automate flow can combine uploads, metadata, Box Doc Gen, Sign, tasks, notifications, and external HTTPS connectors. * **Developer control** — Platform apps authenticate with standard Box auth, call content APIs, start manual workflows, and (where enabled) call Box Automate and Box Doc Gen endpoints. Box is **not** a general-purpose iPaaS for arbitrary SaaS-to-SaaS glue that never touches files. It is strongest when the process is document-centric: intake, classification, generation, review, signature, routing, and archival of content that must remain permissioned and auditable. ### Intelligent workflow capabilities Native, agentic workflow automation on Box content — triggers, AI agents, outcomes, branching, loops, and variables. Best for new intelligent workflows. Template-based document generation from templates and structured data. List and start existing manual-start Box Relay workflows from your app with `GET /workflows` and `POST /workflows/:id/start`. For **new** builds, use **Box Automate**. Existing applications that still list or start manual-start Box Relay workflows can keep using those APIs; prefer Automate for new projects. ### Choose the right capability Start with Box Automate. You get agentic orchestration with triggers, AI, branching, and human review in one runtime. See triggers, outcomes, and logic. Start with Box Automate. Content-aware and external triggers start governed runs without custom workers. See triggers, outcomes, and logic. Use Box Automate Manual Start or Box Relay manual start flows. Both support programmatic start; prefer Box Automate for new builds when it is enabled. Start with Box Doc Gen. It is a purpose-built generation API, and output lands in a Box folder. See generate documents. Use Box Automate + Box Doc Gen. Automate can include document generation as an outcome; the Doc Gen API remains available for direct calls. Use Box Relay. List workflows with `GET /workflows` and start them with `POST /workflows/:id/start`. ## End-to-end intelligent content workflows Diagram showing triggers, AI agents, and outcomes running inside Box Automate as the foundation layer on Box, with an HTTP connector **Box Automate is the foundation layer** for intelligent content workflows. Triggers, AI agents, and outcomes are Automate workflow steps that run on governed Box content. * **Triggers** — Folder and file events, plus Box Forms, start an Automate run when content or form input arrives. * **AI agents** — Custom agents, Box Extract, and Box AI triage and enrich content inside the same Automate workflow. * **Outcomes** — Add metadata, assign tasks, run file and folder actions, request signatures with Box Sign, generate documents with Box Doc Gen, or call external systems through the HTTP connector. Automate itself sits on **Box**, while security and compliance, the content platform, collaboration, and integrations stay underneath every run. Related capabilities often appear in the same solution design: E-signature requests inside or after a workflow. Intake that can trigger Box Automate. Custom agents used where Box Automate AI features are enabled. Structure used for triggers, branching, and extraction. Alternative or complementary event delivery for custom apps outside the builder. ## Box Automate Box Automate is a workflow automation platform built natively on Box. You design **workflows** as directed graphs: a **trigger** starts a run, then **outcomes** execute until the flow ends. The builder supports conditional branching, loops, variables, and data passed between steps — including AI agent output. ### Why build on Box Automate You can stitch the same outcomes together with webhooks, custom workers, and direct Box API calls. Box Automate is the better default when you want **governed automation** rather than another system to operate. * Host workers, queues, retries, and scheduling * Re-implement permission checks on every step * Assemble your own audit story across services * Wire AI, routing, tasks, and Doc Gen by hand * Add custom glue for every new content event * Box runs the workflow runtime, without any automation infrastructure to manage * Steps inherit existing permissions and collaborations * Activity stays on Box content with enterprise audit and governance * Agentic and human steps live in one visual workflow * Content-aware triggers start runs for you Use the Platform APIs when you need fine-grained, app-owned control of a single action. Use Automate when the product is the **process**: multi-step, content-centric work that must stay secure, observable, and maintainable without a private orchestration stack. ### Agentic orchestration with Box Automate Box Automate's strategic differentiation is **agentic orchestration**: workflows that do not only move files on fixed rules, but also invoke AI agents, act on their output, and hand off to people or systems when judgment is required. In practice, that means you can: * **Run custom agents in-flow** — Where enabled for your enterprise, Automate AI capabilities work with custom agents that use knowledge sources, citations, and reasoning traces. See Box AI Studio and the Box Automate overview. * **Ground agents on Box content** — Extraction, summarization, and Q\&A run against the files and folders in the workflow, not a disconnected copy of the data. * **Branch on agent output** — Structured AI results become variables and conditions for later outcomes (route, enrich metadata, generate a document, assign a task, or call an external API). * **Keep humans in the loop** — Approval and general tasks sit beside agent steps so irreversible actions (delete, external share, legal send) can require review. Prefer a review step after AI when results must be verified. * **Orchestrate the full content lifecycle** — One workflow can combine intake, agent triage, Doc Gen, Sign, collaboration, notifications, and HTTPS connectors under Box governance. That combination of agents, content permissions, and durable workflow logic is what makes up a powerful platform for intelligent, agentic workflows. One workflow can also combine intake, agent triage, Doc Gen, Sign, collaboration, notifications, and HTTPS connectors under Box governance. ### Triggers, outcomes, and logic Exact trigger and outcome types depend on plan and configuration. * **File events** — Upload, move, copy, download, delete, preview, lock/unlock, watermark removed, collaborator added, classification applied, and file-with-metadata scenarios. * **Folder events** — Create, move, copy, download, delete, or collaborator added. * **Tasks** — General or approval task completion (including accepted/rejected). * **File Request** — Form completion that deposits content into a folder. * **Box Sign** — Completed, declined, expired, or cancelled. * **Metadata** — Template applied or field changes on a file or folder. * **Manual start** — User starts from the Box web app (also startable via API). * **Form submissions** — When Box Forms are enabled for your plan. * **HTTPS request** — An external system starts a run via a unique URL assigned to the workflow. * **Files and folders** — Move, copy, rename, delete, lock/unlock, watermark, collaborate, classify, and related operations. * **Tasks and notifications** — Assign approval or general tasks; send email with variable-driven content. * **Metadata** — Apply, update, or extract metadata (including with AI). * **AI and agents** — Metadata extraction, summarization, or Q\&A; custom agents with knowledge sources, citations, and reasoning traces where enabled; use structured AI output in later conditions. * **Document generation and signatures** — Box Doc Gen and Box Sign outcomes where enabled. * **External systems** — Custom HTTPS request outcomes (connectors, auth, headers, JSON body), subject to admin allow-lists. * Branching on conditions (for example approval accepted vs rejected, or AI classification result). * **For each** loops over users, files, or folder contents; **loop back** with a max count. * Variables and merging after parallel paths. * JSONPath-style reads of nested payloads when integrating over HTTPS. To learn more, see Triggers, outcomes, and logic. ### Developer integrations Work with your Box admin to enable Box Automate (and related products such as Box AI Studio). See Get started with Box Automate. List Manual Start workflows for a folder and trigger them with files and runtime fields (max 20 files per request). Beta Automate Workflows API (`v2026.0`): list callable Box Automate workflows and start a Box Automate workflow. ## Box Doc Gen Box Doc Gen generates business documents (offer letters, contracts, invoices, agreements, and similar) from **Box Doc Gen templates** stored in Box. Templates are authored in Microsoft Word ([add-in](https://docs.box.com/en/box-doc-gen/box-doc-gen-installation/installing-the-box-doc-gen-template-creator-add-in-for-microsoft-word) or [tagging scripts](https://docs.box.com/en/box-doc-gen/box-doc-gen-templates/template-tags-reference-guide-and-examples)). The API fills tagged fields from JSON and writes output to a destination folder. Box Doc Gen API is available only for **Enterprise Advanced** accounts. Currently, template tags are English-only; validate language requirements before production use. Control your own Microsoft Word access to author templates because Box does not control Word licensing. ### API capabilities Mark files as Box Doc Gen templates. Generate documents (single or batch) from templates and API input. Inspect templates and document generation jobs. All Box Doc Gen API requests must set the `box-version` header to `2025.0`. See Box API versioning. ### Typical generation flow Use a predefined sample template or create one with the Box Doc Gen [add-in](https://docs.box.com/en/box-doc-gen/box-doc-gen-installation/installing-the-box-doc-gen-template-creator-add-in-for-microsoft-word) or [tagging scripts](https://docs.box.com/en/box-doc-gen/box-doc-gen-templates/template-tags-reference-guide-and-examples). Upload or mark the file as a Box Doc Gen template in Box. Call `POST /2.0/docgen_batches` with template file ID, `input_source: api`, JSON `user_input`, `output_type` (`docx` or `pdf`), and destination folder. For more information, see the following developer guides: Get started, Mark template, Templates, and Jobs. ## Learn more Agentic workflows with triggers, AI, branching, and integrations on Box content. Enable Box Automate and trigger Manual Start workflows from the API. Trigger types, outcomes, branching, loops, and variables. Generate business documents from Word templates and API data. Create and manage custom AI agents used in Automate and Box AI. # Get Legal Hold Policy Source: https://developer.box.com/guides/legal-holds/get To get the information for a specific Legal Hold policy that has been created in an enterprise, call the `GET /legal_hold_policies/:id` API endpoint with the `id` of the policy. ```sh cURL theme={null} curl -i -X GET "https://api.box.com/2.0/legal_hold_policies/324432" \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.legalHoldPolicies.getLegalHoldPolicyById(legalHoldPolicyId); ``` ```python Python v10 theme={null} client.legal_hold_policies.get_legal_hold_policy_by_id(legal_hold_policy_id) ``` ```cs .NET v10 theme={null} await client.LegalHoldPolicies.GetLegalHoldPolicyByIdAsync(legalHoldPolicyId: legalHoldPolicyId); ``` ```swift Swift v10 theme={null} try await client.legalHoldPolicies.getLegalHoldPolicyById(legalHoldPolicyId: legalHoldPolicyId) ``` ```java Java v10 theme={null} client.getLegalHoldPolicies().getLegalHoldPolicyById(legalHoldPolicyId) ``` ```java Java v5 theme={null} BoxLegalHoldPolicy policy = new BoxLegalHoldPolicy(api, id); BoxLegalHoldPolicy.Info policyInfo = policy.getInfo(); ``` ```python Python v4 theme={null} legal_hold_policy = client.legal_hold_policy(policy_id='12345').get() print(f'The "{legal_hold_policy.policy_name}" policy is {legal_hold_policy.status}') ``` ```cs .NET v6 theme={null} BoxLegalHoldPolicy policy = await client.LegalHoldPoliciesManager.GetLegalHoldPolicyAsync("11111"); ``` ```javascript Node v4 theme={null} client.legalHoldPolicies.get('11111') .then(policy => { /* policy -> { type: 'legal_hold_policy', id: '11111', policy_name: 'IRS Audit', description: '', status: 'active', assignment_counts: { user: 1, folder: 0, file: 0, file_version: 0 }, created_by: { type: 'user', id: '22222', name: 'Example User', login: 'user@example.com' }, created_at: '2016-05-18T10:28:45-07:00', modified_at: '2016-05-18T11:25:59-07:00', deleted_at: null, filter_started_at: '2016-05-17T01:00:00-07:00', filter_ended_at: '2016-05-21T01:00:00-07:00' } */ }); ``` ## Required Scopes Before using any of the Legal Hold APIs, an application must have the right scopes enabled. See Required Scopes for more details. # Legal Holds Source: https://developer.box.com/guides/legal-holds/index A legal hold is a process that an enterprise can use to preserve all forms of potentially relevant information when litigation is pending or reasonably anticipated. Applying a hold to items prevents any user from deleting them from Box. Legal Holds can be managed and assigned to folders and files through the Box APIs. Legal Holds are a feature of the [Box Governance][governance] package, which can be added on to any Business Plus, Enterprise Advanced or Enterprise account. ## Policies, Assignments, and Holds Working with Legal Hold Policies requires a developer to work with three distinct resources. * **Policies:** A Legal Hold Policy describes the general behavior of the hold. It determines which files should be affected, based on the date and time the files were created or updated. * **Assignments:** A Legal Hold Policy Assignment is a relation between a policy and custodian. In this case, a custodian can be a user, folder, file, or file version. Creating an assignment puts a hold on all the file versions that belong to the custodian. For example, if an assignment is created on a folder the policy is applied to all file versions within that folder. * **Holds**: A File Version Legal Hold represents all the policies that are assigned to a specific file version. Note that every file version can have a maximum of one file version legal hold and this hold contains a list of every assigned policy. ## Example Use Case If an order of discovery is received or the customer is part of an ongoing litigation, a legal hold policy can be created to keep track of everything that needs to be held. The actual holding is done by assigning a policy to a specific file or folder. When the holds are no longer needed, the policy can be released by deleting the assignment. ## Required Scopes Before using any of the Legal Hold APIs, an application must have the GCM and Manage Legal Hold scopes enabled. These are not available in the Developer Console and need to instead be enabled by contacting customer support. [governance]: https://www.box.com/security/governance-and-compliance # List All Legal Hold Policies Source: https://developer.box.com/guides/legal-holds/list To list all Legal Hold Policies that have been created in an enterprise, call the `GET /legal_hold_policies` API endpoint. ```sh cURL theme={null} curl -i -X GET "https://api.box.com/2.0/legal_hold_policies" \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.legalHoldPolicies.getLegalHoldPolicies(); ``` ```python Python v10 theme={null} client.legal_hold_policies.get_legal_hold_policies() ``` ```cs .NET v10 theme={null} await client.LegalHoldPolicies.GetLegalHoldPoliciesAsync(); ``` ```swift Swift v10 theme={null} try await client.legalHoldPolicies.getLegalHoldPolicies() ``` ```java Java v10 theme={null} client.getLegalHoldPolicies().getLegalHoldPolicies() ``` ```java Java v5 theme={null} Iterable policies = BoxLegalHoldPolicy.getAll(api); for (BoxLegalHoldPolicy.Info policyInfo : policies) { // Do something with the legal hold policy. } ``` ```python Python v4 theme={null} policies = client.get_legal_hold_policies() for policy in policies: print(f'Legal Hold Policy "{policy.name}" has ID {policy.id}') ``` ```cs .NET v6 theme={null} BoxCollectionMarkerBased policies = await client.LegalHoldPoliciesManager .GetListLegalHoldPoliciesAsync(); ``` ```javascript Node v4 theme={null} client.legalHoldPolicies.getAll({policy_name: 'Important'}) .then(policies => { /* policies -> { entries: [ { type: 'legal_hold_policy', id: '11111', policy_name: 'Important Policy 1' }, { type: 'legal_hold_policy', id: '22222', policy_name: 'Important Policy 2' } ], limit: 100, order: [ { by: 'policy_name', direction: 'ASC' } ] } */ }); ``` ## Required Scopes Before using any of the Legal Hold APIs, an application must have the right scopes enabled. See Required Scopes for more details. # Get Retention Policy Source: https://developer.box.com/guides/retention-policies/get To get the information for a specific Retention Policy that has been created in an enterprise, call the `GET /retention_policies/:id` API endpoint with the `id` of the policy. ```sh cURL theme={null} curl -i -X GET "https://api.box.com/2.0/retention_policies/982312" \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.retentionPolicies.getRetentionPolicyById(retentionPolicy.id); ``` ```python Python v10 theme={null} client.retention_policies.get_retention_policy_by_id(retention_policy.id) ``` ```cs .NET v10 theme={null} await client.RetentionPolicies.GetRetentionPolicyByIdAsync(retentionPolicyId: retentionPolicy.Id); ``` ```swift Swift v10 theme={null} try await client.retentionPolicies.getRetentionPolicyById(retentionPolicyId: retentionPolicy.id) ``` ```java Java v10 theme={null} client.getRetentionPolicies().getRetentionPolicyById(retentionPolicy.getId()) ``` ```java Java v5 theme={null} // Get the policy name and status for a given retention policy BoxRetentionPolicy policy = new BoxRetentionPolicy(api, id); policy.getInfo("policy_name", "status"); ``` ```python Python v4 theme={null} retention_policy = client.retention_policy(retention_id='12345').get() print(f'Retention Policy ID is {retention_policy.id} and the name is {retention_policy.policy_name}') ``` ```cs .NET v6 theme={null} BoxRetentionPolicy policy = await client.RetentionPoliciesManager.GetRetentionPolicyAsync("11111"); ``` ```javascript Node v4 theme={null} client.retentionPolicies.get('123456789').then((policy) => { /* policy -> { type: 'retention_policy', id: '123456789', policy_name: 'Tax Documents', policy_type: 'indefinite', retention_length: 'indefinite', retention_type: 'modifiable', description: 'Policy to retain all reports', disposition_action: 'remove_retention', can_owner_extend_retention: false, status: 'active', are_owners_notified: true, custom_notification_recipients: [] assignment_counts: { enterprise: 0, folder: 1, metadata_template: 0 }, created_by: { type: 'user', id: '11111', name: 'Example User', login: 'user@example.com' }, created_at: '2015-05-01T11:12:54-07:00', modified_at: '2015-06-08T11:11:50-07:00' } */ }); ``` ## Required Scopes Before using any of the Retention Policy APIs, an application must have the right scopes enabled. See Required Scopes for more details. # Retention Policies Source: https://developer.box.com/guides/retention-policies/index A retention policy blocks permanent deletion of content for a specified amount of time. Admins can create retention policies and then assign them to specific folders or their entire enterprise. Retention policies can be used to keep data for as long as is needed, and then automatically delete the content permanently when the data can no longer be legally held. Retention Policies are a feature of the [Box Governance][governance] package, which can be added on to any Business Plus or Enterprise account. ## Policies, Assignments, and Retentions Working with Retention Policies requires a developer to work with three distinct resources. * **Policies:** A Retention Policy describes the general behavior of the retention policy. It determines how long a retention should stay in place, if it can be extended, and what happens when the retention policy ends. * **Assignments:** A Retention Policy Assignment is a relation between a policy and folder or enterprise. Creating an assignment puts a retention on all the file versions that belong to that folder or enterprise. For example, if an assignment is created on a folder the policy is applied to all file versions within that folder. * **Retentions**: A File Version Retention represents all the policies that are assigned to a specific file version. Note that every file version can have a maximum of one file version retention and that this resource contains a list of every assigned policy. The file version retention section of the Box API is now deprecated. Instead, you can use files under retention or file versions under retention endpoints. ## File Deletion with Retention Policies Files under retention can be deleted from folders, but they will be retained in the trash until the retention expires. When the retention expires, you can choose to have the content automatically deleted or for the policy to be removed. ## Extend Retention for a File Files under retention can have their retention date extended by updating the `disposition_at` field's value with a future date. Once the date has been extended, it cannot be reverted or changed to be prior to the new date. ## Required Scopes Before using any of the Retention Policy APIs, an application must have the following scopes enabled: * Manage Retention Policies, available in the Developer Console under **Configuration** > **Application Scopes** * Global Content Manager (GCM), enabled by contacting customer support [governance]: https://www.box.com/security/governance-and-compliance # List All Retention Policies Source: https://developer.box.com/guides/retention-policies/list To list all Retention Policies that have been created in an enterprise, call the `GET /retention_policies` API endpoint. ```sh cURL theme={null} curl -i -X GET "https://api.box.com/2.0/retention_policies" \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.retentionPolicies.getRetentionPolicies(); ``` ```python Python v10 theme={null} client.retention_policies.get_retention_policies() ``` ```cs .NET v10 theme={null} await client.RetentionPolicies.GetRetentionPoliciesAsync(); ``` ```swift Swift v10 theme={null} try await client.retentionPolicies.getRetentionPolicies() ``` ```java Java v10 theme={null} client.getRetentionPolicies().getRetentionPolicies() ``` ```java Java v5 theme={null} Iterable policies = BoxRetentionPolicy.getAll(api); for (BoxRetentionPolicy.Info policyInfo : policies) { // Do something with the retention policy. } ``` ```python Python v4 theme={null} retention_policies = client.get_retention_policies() for policy in retention_policies: print(f'The policy ID is {policy.id} and the name is {policy.policy_name}') ``` ```cs .NET v6 theme={null} BoxCollectionMarkerBased policies = await client.RetentionPoliciesManager .GetRetentionPoliciesAsync(); ``` ```javascript Node v4 theme={null} client.retentionPolicies.getAll({ policy_name: 'Tax' }).then((policies) => { /* policies -> { entries: [ { type: 'retention_policy', id: '123456789', name: 'Tax Documents' } ], limit: 100, next_marker: 'someMarkerString' } */ }); ``` ## Required Scopes Before using any of the Retention Policy APIs, an application must have the right scopes enabled. See Required Scopes for more details. # Cross-Origin Resource Sharing (CORS) Source: https://developer.box.com/guides/security/cors [Cross-Origin Resource Sharing (CORS)][mdn_cors] is a security mechanism used by web browsers to prevent malicious websites from accessing data on other sites (like the Box API) without explicit permission. CORS only applies to Box API requests made by a web page using a web browser, and it relies on the `HTTP Origin` header being passed along by the browser. It does not come into play in a server-side environment. ## How CORS works When a browser on one domain (for example `company.com`) tries to fetch images, files, or even API resources from another domain (`box.com`), the web browser will prevent access to any of those assets unless the right CORS headers are present. When the browser makes a cross-origin request, an `Origin` request header is passed along with it that contains the domain of the site making that request. This header can not be changed and is part of your web browser's essential security. By default, a browser will not accept any asset loaded from another domain if there is no `Access-Control-Allow-Origin` response header present. Servers like Box can explicitly list the domains allowed to access resources on this server, or they can return a `*` value to allow any domain to access the API. ## How Box uses CORS Box uses the `Origin` request header and `Access-Control-Allow-Origin` response header to enforce CORS rules defined by the developer. ### `Origin`-header validation The Box API validates the `Origin` request header against the list of allowed domains set by the application developer. Multiple allowed origins can be set and any origin not on the list will return in an `HTTP 403` error. ```json theme={null} { "type": "error", "status": 403, "code": "cors_origin_not_whitelisted", "context_info": { "origin": "https://company.com" }, "help_url": "/guides/api-calls/permissions-and-errors/common-errors/", "message": "Access denied - Did you forget to safelist your origin in the CORS config of your app?", "request_id": "4dsdfsa832213" } ``` If no origin is set, all requests to the Box API for this application return an error. ### `Access-Control-Allow-Origin` response header After the Box API has validated the `Origin` header, it will return the data requested as well as an `Access-Control-Allow-Origin` response header with the value `*`. ```yaml theme={null} HTTP/1.1 200 OK Date: Wed, 23 Sep 2020 14:07:29 GMT Content-Type: application/json Transfer-Encoding: chunked Connection: keep-alive Strict-Transport-Security: max-age=31536000 Cache-Control: no-cache, no-store Access-Control-Allow-Origin: * Vary: Origin BOX-REQUEST-ID: 032cfb446dae4fd0b4c2bff80a1a97ba7 ``` By returning this header, the Box API informs the web browser that the response can be used in the site that requested the data. ## Enabling CORS for your domain To enable CORS for the domain your application runs on, head over to the developer console, select your application, and scroll down to the bottom of the **Configuration** panel to find the **CORS Domains** setting. Add a comma separated list of all the origins that you expect your application to be making Box API requests from. Domains require the schema (`http` or `https`) and can include wildcards for subdomains, for example `*.example.com`. If your site runs on a non-standard port, it will also need to include that. This is especially relevant for a site running on `localhost` or `127.0.0.1`. An example list of origins would be as follows. ```sh theme={null} https://company.com,https://*.internal.company.com,http://localhost:3000 ``` ## Debugging CORS There are a few different kinds of CORS errors that might occur when making API calls to the Box API. ### `HTTP 403` - No allowed origins defined You might get this error even after you provided a list of origins. Often, this is because of a typo in the provided origins. 1. **Check your origins** - Head back to the developer console and make sure your origins map the site you are making the API call from. Keep in mind that an origin includes the scheme (`http(s)`) but no path or trailing `/`. We recommend inspecting the page using your browser's debug console and checking the `Origin` request header value. This value should match one of the provided values in the developer console. 2. **Check your credentials** - Another reason for this error might be that you are authenticating as a different application than the one you have set the origin up for. Check that the credentials match the ones of the application you are intending to use. We recommend trying to make a call from a server-side script to validate that the API call works. ### `Cross-Origin Request Blocked` In some cases, you might get a Javascript error that mentions CORS. ```sh theme={null} Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://api.box.com/2.0/users/me. (Reason: CORS request did not succeed). ``` In many cases this has little to do with CORS. Instead we recommend checking the following. 1. **Check your authentication headers** - If the authorization header is not provided or malformed, then the API will return a generic error without the necessary `Access-Control-Allow-Origin` header. This in turn will cause the previously mentioned error to be raised by your browser. Make sure to pass in an access token using the `Authorization: Bearer ...` header. 2. **Check for requests blocked by VPN, Proxies, etc** - In some cases, the Box API might be blocked by your VPN, corporate proxy, a browser extension, your DNS provider, or any other service that can intercept network traffic. Any of these can intercept the request and return a whole new request that does not include the necessary `Access-Control-Allow-Origin` header. To test for this case, try to make the same API call from a non-browser environment, from an incognito window, or from a whole other (not company owned) device. [mdn_cors]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS ### `Access-Control-Allow-Origin` header issues If you encounter issues with the `Access-Control-Allow-Origin` header, do the following: 1. **Check if your domain is on the list of allowed origins** - Go to the developer console and open your application. Click on the **Configuration** tab and scroll down. You can add your domain to the list in section **CORS domains**. CORS allowlist 2. **Check if your server is set up correctly** - Configure your server to handle cross-domain requests or use non-cross-domain requests if you receive a warning **No 'access-control-allow-origin' header is present on the requested resource**. # Device Pinners Source: https://developer.box.com/guides/security/device-pinners Building on the login tracking feature – which allows admins to set limits on the number of devices a user can access Box from and sends alerts to them and the user whenever a new device is used to access that Box account – Box has additional device management functionality to increase security when accessing Box on mobile or desktop devices: device pinning. To learn more about device pinning, please see our [community documentation][community]. ## APIs The Box API allows for device pins to be inspected and removed. * `GET /enterprise/:id/device_pinners`: Retrieves all the device pins within an enterprise. * `GET /device_pinners/:id`: Retrieves information about an individual device pin. * `DELETE /device_pinners/:id`: Deletes an individual device pin. [community]: https://support.box.com/hc/en-us/articles/360043693814-Device-Pinning-Settings # FedRAMP Source: https://developer.box.com/guides/security/fedramp ## Overview FedRAMP is a certification program that allows federal agencies to use cloud providers for increasingly secure/sensitive government or government-adjacent data. FedRAMP defines three categories regarding levels of security, Low, Moderate, and High. The higher the security level the more restrictions are in place. Box is currently certified as [FedRAMP High][FedRAMPCert]. ## Considerations In order to be FedRAMP compliant, your administrator must set up Box in a very specific way. It is possible that the administrator has further restricted access to Box functionalities. Consult with your administrator to identify security restrictions in place that might affect the usage of the API. ## API usage in FedRAMP For FedRAMP compliance, you may use the below URLs for API entry points. | FedRAMP | | - | | account.box.com | | api.box.com | | upload.box.com | | dl.boxcloud.com | | realtime.services.box.net | [FedRAMPCert]: https://marketplace.fedramp.gov/products/F1212191840A # Security Source: https://developer.box.com/guides/security/index Whether you are a developer getting started with the Box API or a Box Admin tasked with authorizing applications, it is critical you understand the security mechanisms in place to protect content stored in Box. The Box API follows the same security principles and restrictions as the Box web app. This means that you will not be able to bypass content [permissions][perm], the [waterfall folder structure][waterfall], or Admin-only requirements by leveraging the Box API. ## Access Tokens At the core of every Box API call is an Access Token. Because a username and password cannot be used, the Box servers need a way of validating user identity. The full capability of an Access Token encompasses user permissions, token permissions, and application settings. Access Token Components Access Tokens represent the authenticated user and determine what content a user can successfully call. Similar to using the Box Web App, you will only be able to successfully interact with content the user, associated with the Access Token, either owns or is a collaborator on. This can be further restricted by downscoping a token. Access Tokens are only valid for 60 minutes, but can be revoked earlier if needed. Once an Access Token expires, when using an OAuth 2.0 application, a Refresh Token can be exchanged for another Access Token. Refresh tokens are valid for 60 days or one use. Alternatively, when using a server authentication application, the request Access Token endpoint must be called for a new Access Token. For security reasons we do not allow long-lived access tokens. Unsure why you are receiving a 404 error? A great place to start is checking to see what user is associated with your Access Token by using the get current user endpoint. ## Scopes Scopes Scopes are configured in the [Developer Console][dc] upon application creation. They determine which of the 150+ endpoints Access Tokens of an application can successfully call. Because scopes work in conjunction with user permissions, granting the write scope does not automatically provide a user with API access to all content in a Box enterprise. Instead, it means that the authenticated user can receive successful API responses when making write calls to content they have access to. For example, take an application with only the manage users and manage groups scopes enabled. If an Access Token of this application tried to make an API call to get information about a folder, even if the associated user owned it, it would receive a 403 error. This is because the read scope is required to perform this action. Access Tokens of this application could only receive successful responses on API calls related to users and groups. ## Restricted endpoints There are some API endpoints that only Admins or Co-Admins, granted the appropriate [permissions][coadminperm], can successfully use. As a general rule of thumb, if only an Admin or Co-Admin can perform an action via the Box Admin Console, an Access Token associated with one of these users is required to complete an API call for the same action. This is called out in our API reference documentation for a given endpoint if it is required. Some Admin-restricted endpoints include: * Creating, deleting, or getting information about users * Creating, deleting, or modifying groups * Viewing user or enterprise events Other endpoints can only be used by an Admin user's Access Token if the enterprise has purchased add-on products such as Box Governance or Box Shield. Some of these endpoints include: * Interacting with security classifications * Interacting with legal hold policies and assignments * Interacting with retention policies and assignments ## Application Access Application Access Settings Application access is only configured in the [Developer Console][dc] for applications leveraging Server Authentication with JWT or Client Credentials Grant. This setting determines the types of users that can be used with the application. The two options are **app access only** or **app + enterprise access**. Upon authorizing one of these applications in the Box Admin Console, a Service Account (`AutomationUser_xxxx_@boxdevedition.com`) representing the application is automatically generated. This account is an Admin-like user that can only be accessed via the API and can then be used to create users of the application called App Users. If an application only needs to interact with the Service Account and App Users, or with content they own or are collaborated on, **app only access** must be selected. If an application needs to interact with managed users and their existing Box content, app + enterprise access must be selected. As an example, take a JWT application that has the read/write scopes, app only access, and is properly authorized in the Admin console. If a managed user obtains an Access Token and makes an API call to a folder they own, that call would receive a 400 error with the message “Cannot obtain token based on the enterprise configuration for your app”. Even though the user has access to the content, the correct scopes are enabled and the app is authorized, the selected application access only allows the application to interact with the Service Account and App Users. ## Enterprise settings and authorization There are a few enterprise settings to be aware of when it comes to the Box API. Global Integration Settings Platform applications fall into two categories: published and unpublished. Published applications are found in the [Box Integrations][appcenter]. Box Admins decide whether published and unpublished applications are enabled by default and therefore can be used without approval. The status of these settings determines what actions are necessary to successfully authorize an application for use. Admin Console Apps Tab Regardless of the settings above, in order for an application leveraging JWT or Client Credentials Grant to be used by an enterprise, an Admin must explicitly authorize it via the Box Admin console. The authorization is a snapshot in time. This means that if a developer revisits the Developer Console and changes the configuration, the Admin must re-authorize the application in order for generated Access Tokens to reflect the changes. If the setting **Disable unpublished apps by default** is turned on, an Admin must also explicitly enable any application leveraging OAuth 2.0 as the authentication method. Additionally, if this setting is turned on, Server Authenticated apps will also require enablement. [perm]: https://support.box.com/hc/en-us/articles/360044196413-Understanding-Collaborator-Permission-Levels [waterfall]: https://support.box.com/hc/en-us/articles/360043697254-Understanding-Folder-Permissions [coadminperm]: https://support.box.com/hc/en-us/articles/360044194393-Granting-And-Modifying-Co-Admin-Permissions [dc]: https://app.box.com/developers/console [appcenter]: https://app.box.com/services # Application Flow Source: https://developer.box.com/guides/security/terms-of-service/flow In general, applications use Terms of Services as follows. When an application, authenticated as a user, tries to access an item in Box that requires the user to have accepted the relevant Terms of Service it receives a `TERMS_OF_SERVICE_REQUIRED` error. ```json theme={null} { "type": "error", "status": 400, "code": "terms_of_service_required", "context_info": { "tos_id": 261346614, "tos_user_status_id": 4562456 }, "help_url": "/guides/api-calls/permissions-and-errors/common-errors/", "message": "User must accept custom terms of service before action can be taken", "request_id": "ADF7722DD" } ``` The application requests the Terms of Service's information by calling `GET /terms_of_services/:id`. ```json theme={null} { "id": 261346614, "type": "terms_of_service", "status": "enabled", "enterprise": { "id": 11446498, "type": "enterprise", "name": "Acme Inc." }, "tos_type": "managed", "text": "By using this service, you agree to ...", "created_at": "2012-12-12T10:53:43-08:00", "modified_at": "2012-12-12T10:53:43-08:00" } ``` The application can then show the text from the Terms of Service to the user. When the user accepts or rejects the terms, it makes a call to either `PUT /terms_of_service_user_statuses/:id` or `POST /terms_of_service_user_statuses` depending on if the initial error returned a `tos_user_status_id` in the response. ## Server authentication and acting on behalf of users Applications using JWT, Client Credentials Grant (CCG), or OAuth 2.0 may act as a service account, an App User, or a managed user. Terms of Service enforcement depends on which user is in context for the API request. | Scenario | Blocked if Managed Terms of Service not accepted? | | - | - | | API call with a service account or App User token (no `As-User`) | **No** — headless users are exempt | | API call with CCG/JWT and `As-User` set to a managed user | **Yes** — the user specified in the `As-User` header must have accepted | | User access token issued for a managed user | **Yes** — token issuance is blocked until Terms of Service is accepted | | OAuth authorization code flow for a managed user | **Yes** — authorization is blocked until Terms of Service is accepted | | API call with `As-User` set to a service account or App User | **No** — headless users are exempt | ### Accepting Terms of Service programmatically When a managed user has not accepted Managed Terms of Service, most API calls made on their behalf return `terms_of_service_required`. To resolve this without requiring the user to sign in to the Box web application: 1. Obtain a server authentication access token (JWT or CCG). 2. Set the `As-User` header to the managed user's ID so subsequent requests run in that user's context. 3. Call the Terms of Service endpoints, which remain available even when Terms of Service acceptance is outstanding: * `GET /terms_of_services/:id` to retrieve the terms text * `POST /terms_of_service_user_statuses` or `PUT /terms_of_service_user_statuses/:id` to accept or reject 4. Retry the original API call. An admin cannot accept Managed Terms of Service for another user without using the `As-User` header to act as that user. Acceptance must be recorded for the user who is subject to the Terms of Service. # Find Terms for Collaboration Source: https://developer.box.com/guides/security/terms-of-service/for-colaboration Information about the Terms of Service that is in effect for any Collaboration can be inspected by calling the `GET /collaborations/:id` API and passing the query parameter `fields=acceptance_requirements_status`. ```sh cURL theme={null} curl -X GET https://api.box.com/2.0/collaborations/2342342?fields=acceptance_requirements_status \ -H "authorization: Bearer " ``` The resulting response will include a new `acceptance_requirements` object that includes a mini `terms_of_service` object. ```json theme={null} { "type": "collaboration", "id": 2342342>, "acceptance_requirements": { "terms_of_service": { "type": "terms_of_service", "id": 6766677 } } } ``` This information is only returned if the Terms of Service for external users is enabled for the enterprise, and the user making the request has the permission to see the Terms of Service. This holds true for both admin and end users, even though admins can generally view Terms of User information via the API even if the specific Terms of Service type is turned off. If the Terms of Service type is not enabled, the API will return an empty result. ```json theme={null} { "type": "collaboration", "id": 2342342>, "acceptance_requirements": { "terms_of_service": null } } ``` The `terms_of_service` information is returned within the `acceptance_requirements` even if they have already been accepted by the user. # Terms of Service Source: https://developer.box.com/guides/security/terms-of-service/index The Box API allows administrators to configure Terms of Services for working on Box, and for users to accept & re-accept Terms of Services for custom applications. ## Terminology ### Terms of Services A Terms of Service is an enterprise-level record that represents the conditions within which all users are allowed to work with an enterprise's data in Box. There are currently two types of Terms of Service for any enterprise that can be enabled independently. The **Managed Terms of Service** can be enabled for the enterprise's own users, where the **External Terms of Service** can be enabled for users from other enterprises that collaborated on the primary enterprise's data. ### Who is subject to Managed Terms of Service? When Managed Terms of Service is enabled for an enterprise, it applies to **login-capable managed users** in that enterprise. These are users who can sign in to Box (for example, standard managed users with credentials). The following user types are **not** subject to Managed Terms of Service, even though they belong to the enterprise: | User type | Managed Terms of Service required? | | - | - | | Admin/Co-Admin and Managed user (can log in to Box) | **Yes** | | Service account (Automation User) | **No** | | App User | **No** | Service accounts and App Users are API-only, headless users. They are exempt from Managed Terms of Service enforcement. The Admin Console setting for managed users refers to these login-capable users, not every enterprise member. ## Terms of Service User Statuses A Terms of Service User Status represents the status of the Terms of Service acceptance for a specific user. There is exactly one Terms of Service User Status for any given combination of Terms of Service and a user. There are multiple Terms of Service User Statuses for any Terms of Service, specifically one for each user. There could be multiple Terms of Service User Statuses for a given user. The user could accept or reject multiple External Terms of Services for different enterprises they have been collaborated into, in addition to accepting or rejecting their own enterprise’s Managed Terms of Service. ## APIs Applications that are authenticated as a Box Admin that has the **Edit settings for your company** permissions can view, create, and edit Terms of Services for their enterprise via the API. * `GET /terms_of_services/:id`: To get the information for a specific Terms of Service * `GET /terms_of_services`: To get a list of all the Terms of Services used within an enterprise, either for managed or external users. * `POST /terms_of_services`: To create Terms of Service settings for either an external or managed user. * `PUT /terms_of_services/:id`: To update a specific Terms of Service setting Additionally, an application can view and accept Terms of Services for a regular user via the API. * `GET /terms_of_service_user_statuses`: To get a list of all the Terms of Services for a user * `POST /terms_of_service_user_statuses`: To accept or reject a specific Terms of Service for the first time * `PUT /terms_of_service_user_statuses/:id`: To accept or reject a specific Terms of Service that had been previously accepted or rejected. ## Scopes The following scopes should be granted to the application in order to take the outlined actions. * **Manage Enterprise Properties**: Required to enable or edit the enterprise's settings for Terms of Services as well as to view them for external users. * **Manage Users**: Required to accept Terms of Services for other users. # Permissions Source: https://developer.box.com/guides/security/terms-of-service/permissions The following is a list of permissions users, admins, and co-admins need to have when working with Terms of Services and Terms of Service Statuses. ## Terms of Service An end user is considered subject to Terms of Service when: * User belongs to or is collaborated into an enterprise that has a Terms of Service enabled * The type of Terms of Service reflects the user's relationship to the Enterprise * A managed Terms of Service for a user that is part of the same enterprise * An external Terms of Service for users collaborating into the enterprise For **Managed** Terms of Service specifically, the user must also be a login-capable managed user. Service accounts and App Users are headless users and are not subject to Managed Terms of Service, even when they belong to the enterprise. Terms of Service settings can be viewed by an end user if: * The user is subject to a Terms of Service; and * The Terms of Service Type is enabled on the enterprise A Terms of Service's settings can be viewed by an enterprise admin or co-admin if: * They have **View settings for your company** permissions * The application has the **Manage enterprise properties** scope enabled * The Terms of Service belongs to their enterprise Terms of Service settings can be edited by an enterprise admin or co-admin if: * They have **Edit settings for your company** permissions * The application has the **Manage enterprise properties** scope enabled * The Terms of Service belongs to their enterprise Enterprise admins and co-admins can view, create, and edit Terms of Service settings for both external and managed Terms of Service without having accepted the managed Terms of Service for their own enterprise. ## Terms of Service User Status Terms of Service User Status can be viewed and edited by an end user if: * The User Status belongs to the end user * The Terms of Service type is enabled on the enterprise * The end user is subject to the Terms of Service Terms of Service User Statuses belonging to other users can be viewed by enterprise admins and co-admins if: * They have **Manage users** permissions * The application has the **Manage users** scope enabled * The Terms of Service belongs to their enterprise * They have accepted the managed Terms of Service for their own enterprise, if applicable Terms of Service User Status belonging to other users can be edited by enterprise admins and co-admins if: * They have **Manage users** permissions * The application has the **Manage users** scope enabled * The application has the **As-User** scope enabled * The end user is subject to the Terms of Service * The end user is not an admin or co-admin * The Terms of Service belongs to their enterprise * They have accepted the managed Terms of Service for their own enterprise, if applicable An end user cannot accept, reject, view external Terms of Service settings for an enterprise they are collaborating into until the end user accepts the managed Terms of service for their own enterprise, where applicable. Trying to do so will result in a `TERMS_OF_SERVICE_REQUIRED` error. # Bulk delete external users Source: https://developer.box.com/guides/users/bulk-delete-external-users You can remove up to 100 external users from your enterprise using the API. This API endpoint removes access to all types of content you invited the listed external users to collaborate on. To remove the external users, call `POST /external_users/submit_delete_job`. This endpoint requires API version `2025.0`. Include the `box-version: 2025.0` header in your request. ```sh cURL theme={null} curl -i -X POST "https://api.box.com/2.0/external_users/submit_delete_job" \ -H "box-version: 2025.0" \ -H "authorization: Bearer " \ -H "content-type: application/json" \ -d '{ "external_users": [ { "type": "user", "id": "" } ] }' ``` This job runs in the background, and sends a completion report listing deletion status for each user when it's finished. When you delete external users, their pending collaboration invites are not deleted. # Create App User Source: https://developer.box.com/guides/users/create-app-user App Users are programmatic user accounts that can be created by apps using server authentication (JWT or Client Credentials Grant). They represent users, groups, or processes behind the scenes in your application without requiring a Box account to log in. Before you can create App Users, your JWT or CCG application must be authorized in the Admin Console, which generates a Service Account. You use the Service Account's access token to create App Users through the API. App Users can only be accessed through the Box APIs and don't have credentials to log in to `box.com` directly. ## Common App User Patterns Typically app users are created for a number of different patterns: * To represent a single application user or group of users without a `box.com` account. * To represent an application process, such as having the app user monitor all events within an enterprise. * To provide the application with the ability to completely control the file and folder structure of a user account without the possibility of that content being modified through the `box.com` web app. ## Creating a New App User To generate a new app user, the minimal information that will be required will be a name for the app user. ```java Java v5 theme={null} BoxUser.Info createdUserInfo = BoxUser.createAppUser(api, "A User"); ``` ```python Python v4 theme={null} new_app_user = client.create_user('App User 123', login=None) ``` ```cs .NET v6 theme={null} var userParams = new BoxUserRequest() { Name = "App User 12", ExternalAppUserId = "external-id", IsPlatformAccessOnly = true }; BoxUser newUser = await client.UsersManager.CreateEnterpriseUserAsync(userParams); ``` To see all available optional parameters that may be set when creating an app user, see the create user endpoint. Before you can make any changes to the newly created account, you need to click the link you receive in the confirmation email. Once the app user is created a user object will be returned. Within the user object is an ID for the app user, which may be used to make API requests to modify the user in the future. # Create Managed User Source: https://developer.box.com/guides/users/create-managed-user To generate a new managed user, the minimal information that will be required will be a name and an email address for the managed user. The email address supplied when creating a managed user must be unique. It cannot already be associated with an existing Box user. ```sh cURL theme={null} curl -i -X POST "https://api.box.com/2.0/users" \ -H "authorization: Bearer " \ -H "content-type: application/json" \ -d '{ "login": "ceo@example.com", "name": "Aaron Levie" }' ``` ```typescript Node/TypeScript v10 theme={null} await client.users.createUser({ name: userName, login: userLogin, isPlatformAccessOnly: true, } satisfies CreateUserRequestBody); ``` ```python Python v10 theme={null} client.users.create_user(user_name, login=user_login, is_platform_access_only=True) ``` ```cs .NET v10 theme={null} await client.Users.CreateUserAsync(requestBody: new CreateUserRequestBody(name: userName) { Login = userLogin, IsPlatformAccessOnly = true }); ``` ```swift Swift v10 theme={null} try await client.users.createUser(requestBody: CreateUserRequestBody(name: userName, login: userLogin, isPlatformAccessOnly: true)) ``` ```java Java v10 theme={null} client.getUsers().createUser(new CreateUserRequestBody.Builder(userName).login(userLogin).isPlatformAccessOnly(true).build()) ``` ```java Java v5 theme={null} BoxUser.Info createdUserInfo = BoxUser.createEnterpriseUser(api, "user@example.com", "A User"); ``` ```python Python v4 theme={null} new_user = client.create_user('Temp User', 'user@example.com') ``` ```cs .NET v6 theme={null} var userParams = new BoxUserRequest() { Name = "Example User", Login = "user@example.com" }; BoxUser newUser = await client.UsersManager.CreateEnterpriseUserAsync(userParams); ``` ```javascript Node v4 theme={null} client.enterprise.addUser( 'eddard@winterfell.example.com', 'Ned Stark', { role: client.enterprise.userRoles.COADMIN, address: '555 Box Lane', status: client.enterprise.userStatuses.CANNOT_DELETE_OR_EDIT }) .then(user => { /* user -> { type: 'user', id: '44444', name: 'Ned Stark', login: 'eddard@winterfell.example.com', created_at: '2012-11-15T16:34:28-08:00', modified_at: '2012-11-15T16:34:29-08:00', role: 'coadmin', language: 'en', timezone: 'America/Los_Angeles', space_amount: 5368709120, space_used: 0, max_upload_size: 2147483648, status: 'active', job_title: '', phone: '', address: '555 Box Lane', avatar_url: 'https://www.box.com/api/avatar/large/deprecated' } */ }); ``` To see all available optional parameters that may be set when creating an app user, see the create user endpoint. Before you can make any changes to the newly created account, you need to click the link you receive in the confirmation email. A user object will be returned from the create user request. Within the user object is an ID for the managed user, which may be used to make API requests to modify the user in the future. Once a new managed user is created the email address used will receive an email from Box asking them to create a password for the account. The account will be in a `pending` state until that action has taken place. For security reasons passwords cannot be supplied when creating a new managed user. # Delete User Source: https://developer.box.com/guides/users/delete-user The process for deleting both app and managed users is the same. To delete a user account, supply the user ID for the account that should be removed. ```sh cURL theme={null} curl -i -X DELETE "https://api.box.com/2.0/users/12345" \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.users.deleteUserById(user.id); ``` ```python Python v10 theme={null} client.users.delete_user_by_id(user.id) ``` ```cs .NET v10 theme={null} await client.Users.DeleteUserByIdAsync(userId: user.Id); ``` ```swift Swift v10 theme={null} try await client.users.deleteUserById(userId: user.id) ``` ```java Java v10 theme={null} client.getUsers().deleteUserById(user.getId()) ``` ```java Java v5 theme={null} BoxUser user = new BoxUser(api, "0"); user.delete(false, false); ``` ```python Python v4 theme={null} user_id = '33333' client.user(user_id).delete(force=True) ``` ```cs .NET v6 theme={null} await client.UsersManager.DeleteEnterpriseUserAsync("44444", notify: false, force: true); ``` ```javascript Node v4 theme={null} // Delete the user even if they still have files in their account client.users.delete('123', {force: true}) .then(() => { // deletion succeeded — no value returned }); ``` There are also two optional parameters that may be set when deleting a user account: * force: Whether the user should be deleted even if the account still has content in it. * notify: Whether the user will receive a notification that the account was deleted. The delete user request will fail if the user account still has content in it. To resolve this, either transfer all files or folders to another account or use the optional `force` parameter. # Deprovision Users Source: https://developer.box.com/guides/users/deprovision/index Part of regular Box enterprise maintenance is removing accounts for users that are no longer active in your enterprise. When removing a user from your enterprise, you'll need to move all content owned by the user into another account before deleting the user account. The delete user request will fail if the user account still has content in it. An optional `force` parameter is available in the API call, which will force delete the user account along with all content in the account. The standard best practice when decommissioning a user account is to move all content owned by that user into another admin level account or into the application service account. Once moved, you can transfer ownership of the content to a different user or collaborate a different user on the content if needed. ## Deprovisioning Example Use the following code samples to transfer a user's content and then delete the user. When content is being transferred, a new folder is created in the destination user's root folder following this pattern: `employee_email@email.com - employee_name's Files and Folders` ```js theme={null} 'use strict' const box = require('box-node-sdk'); const fs = require('fs'); let configFile = fs.readFileSync('config.json'); configFile = JSON.parse(configFile); let session = box.getPreconfiguredInstance(configFile); let serviceAccountClient = session.getAppAuthClient('enterprise'); const transferUserID = '3278487052'; (async () => { let serviceAccount = await serviceAccountClient.users.get('me'); let transferredFolder = await serviceAccountClient.enterprise.transferUserContent(transferUserID,serviceAccount.id); console.log(transferredFolder); await serviceAccountClient.users.delete(transferUserID, null); console.log('Completed'); })(); ``` ```java theme={null} Path configPath = Paths.get("config.json"); try (BufferedReader reader = Files.newBufferedReader(configPath,Charset.forName("UTF-8"))){ String transferUserId = "3277722534"; BoxConfig boxConfig = BoxConfig.readFrom(reader); BoxDeveloperEditionAPIConnection serviceAccountClient = BoxDeveloperEditionAPIConnection .getAppEnterpriseConnection(boxConfig); BoxUser destinationUser = new BoxUser(serviceAccountClient, BoxUser.getCurrentUser(serviceAccountClient).getID()); try { destinationUser.moveFolderToUser(transferUserId); } catch (BoxAPIException e) {} BoxUser removeUser = new BoxUser(serviceAccountClient, transferUserId); removeUser.delete(false, false); } ``` ```csharp theme={null} using(FileStream fs = new FileStream("./config.json", FileMode.Open)) { var config = BoxConfig.CreateFromJsonFile(fs); var session = new BoxJWTAuth(config); var serviceAccountClient = session.AdminClient(session.AdminToken()); var transferUserId = "3276247601"; var serviceAccount = await serviceAccountClient.UsersManager.GetCurrentUserInformationAsync(); var moveAction = await serviceAccountClient.UsersManager.MoveUserFolderAsync(transferUserId,serviceAccount.Id); System.Console.WriteLine(moveAction.Name); await serviceAccountClient.UsersManager.DeleteEnterpriseUserAsync(transferUserId,false,false); } ``` ```shell theme={null} box users:transfer-content $transfer_from_user_id $transfer_to_user_id box users:delete $transfer_from_user_id --yes ``` # Transfer Files & Folders Source: https://developer.box.com/guides/users/deprovision/transfer-folders As part of user account deprovisioning, a common requirement is to transfer all files and folders that are stored within the user account to another user account or into a location for long term storage, such as into the service account. There are two general methods that are employed to accomplish this within Box: * Using the direct transfer owned folders API, which will move all content from one user directly to another. * Using the collaboration transfer method to change ownership of one file or folder at a time from one user to another. Files owned by a user will be inaccessible while they are being transferred. This also means that any shared content owned by the user may be inaccessible during the move. Depending on the volume of content, this operation may take a significant amount of time. ## Transfer Owned Folders API Method The transfer owned folders endpoint is designed to move the entirety of content owned by one user over to another user. The transfer owned folders API is performed as a synchronous process, which might lead to a slow response when the source user has a large number of items in all of its folders. To call the transfer endpoint, you will supply the user ID to transfer from and the user ID to transfer to. ```typescript Node/TypeScript v10 theme={null} await client.transfer.transferOwnedFolder( sourceUser.id, { ownedBy: { id: targetUser.id, } satisfies TransferOwnedFolderRequestBodyOwnedByField, } satisfies TransferOwnedFolderRequestBody, { queryParams: { notify: false } satisfies TransferOwnedFolderQueryParams, } satisfies TransferOwnedFolderOptionalsInput, ); ``` ```python Python v10 theme={null} client.transfer.transfer_owned_folder( source_user.id, TransferOwnedFolderOwnedBy(id=target_user.id), notify=False ) ``` ```cs .NET v10 theme={null} await client.Transfer.TransferOwnedFolderAsync(userId: sourceUser.Id, requestBody: new TransferOwnedFolderRequestBody(ownedBy: new TransferOwnedFolderRequestBodyOwnedByField(id: targetUser.Id)), queryParams: new TransferOwnedFolderQueryParams() { Notify = false }); ``` ```swift Swift v10 theme={null} try await client.transfer.transferOwnedFolder(userId: sourceUser.id, requestBody: TransferOwnedFolderRequestBody(ownedBy: TransferOwnedFolderRequestBodyOwnedByField(id: targetUser.id)), queryParams: TransferOwnedFolderQueryParams(notify: false)) ``` ```java Java v10 theme={null} client.getTransfer().transferOwnedFolder(sourceUser.getId(), new TransferOwnedFolderRequestBody(new TransferOwnedFolderRequestBodyOwnedByField(targetUser.getId())), new TransferOwnedFolderQueryParams.Builder().notify(false).build()) ``` ```java Java v5 theme={null} String sourceUserID = "11111"; String destinationUserID = "22222"; BoxUser sourceUser = new BoxUser(api, sourceUserID); BoxFolder.Info transferredFolderInfo = sourceUser.transferContent(destinationUserID); ``` ```python Python v4 theme={null} source_user_id = '33333' destination_user_id = '44444' user = client.user(source_user_id) destination_user = client.user(destination_user_id) folder = user.transfer_content(destination_user) print(f'Created new folder "{folder.name}" in the account of user {destination_user.id}') ``` ```cs .NET v6 theme={null} var sourceUserId = "33333"; var destinationUserId = "44444"; BoxFolder movedFolder = await client.MoveUserFolderAsync(sourceUserId, destinationUserId); ``` ```javascript Node v4 theme={null} var sourceUserID = '33333'; var destinationUserID = '44444'; client.enterprise.transferUserContent(sourceUserID, destinationUserID) .then(movedFolder => { /* movedFolder -> { type: 'folder', id: '123456789', sequence_id: '1', etag: '1', name: 'Other User's Files and Folders', created_at: '2018-04-23T11:00:07-07:00', modified_at: '2018-04-23T11:00:07-07:00', description: 'This folder contains files previously owned by Other User, and were transferred to you by your enterprise administrator. If you have any questions, please contact Enterprise Admin (admin@example.com).', size: 0, path_collection: { total_count: 1, entries: [ { type: 'folder', id: '0', sequence_id: null, etag: null, name: 'All Files' } ] }, created_by: { type: 'user', id: '99999', name: 'Enterprise Admin', login: 'admin@example.com' }, modified_by: { type: 'user', id: '99999', name: 'Enterprise Admin', login: 'admin@example.com' }, trashed_at: null, purged_at: null, content_created_at: '2018-04-23T11:00:07-07:00', content_modified_at: '2018-04-23T11:00:07-07:00', owned_by: { type: 'user', id: '33333', name: 'Example User', login: 'user@example.com' }, shared_link: null, folder_upload_email: null, parent: { type: 'folder', id: '0', sequence_id: null, etag: null, name: 'All Files' }, item_status: 'active' } */ }); ``` ## Collaboration Transfer Method The collaboration transfer method is a process that uses the collaboration endpoint to change the ownership of a single file or folder from one user to another instantaneously. This method will perform an instantaneous transfer of ownership of a single file or folder, but **cannot** be used to transfer the root (all files and folders) from one user to another. The general process, between `transfer_from_user` to `transfer_to_user`, will follow these steps: ### Add Transfer To User as Co-Owner The first step is to add the `transfer_to_user` account as a collaborator with `co-owner` access on the file or folder that should be transferred. Making the call as the `transfer_from_user` account, add the `transfer_to_user` as a co-owner using the add collaboration endpoint. ```sh cURL theme={null} curl -i -X POST "https://api.box.com/2.0/collaborations" \ -H "authorization: Bearer " \ -H "content-type: application/json" \ -d '{ "item": { "type": "file", "id": "11446498" }, "accessible_by": { "type": "user", "login": "user@example.com" }, "role": "editor" }' ``` ```typescript Node/TypeScript v10 theme={null} await client.userCollaborations.createCollaboration({ item: { type: 'folder' as CreateCollaborationRequestBodyItemTypeField, id: folder.id, } satisfies CreateCollaborationRequestBodyItemField, accessibleBy: { type: 'user' as CreateCollaborationRequestBodyAccessibleByTypeField, id: user.id, } satisfies CreateCollaborationRequestBodyAccessibleByField, role: 'editor' as CreateCollaborationRequestBodyRoleField, } satisfies CreateCollaborationRequestBody); ``` ```python Python v10 theme={null} client.user_collaborations.create_collaboration( CreateCollaborationItem(type=CreateCollaborationItemTypeField.FOLDER, id=folder.id), CreateCollaborationAccessibleBy( type=CreateCollaborationAccessibleByTypeField.USER, id=user.id ), CreateCollaborationRole.EDITOR, ) ``` ```cs .NET v10 theme={null} await client.UserCollaborations.CreateCollaborationAsync(requestBody: new CreateCollaborationRequestBody(item: new CreateCollaborationRequestBodyItemField() { Type = CreateCollaborationRequestBodyItemTypeField.Folder, Id = folder.Id }, accessibleBy: new CreateCollaborationRequestBodyAccessibleByField(type: CreateCollaborationRequestBodyAccessibleByTypeField.User) { Id = user.Id }, role: CreateCollaborationRequestBodyRoleField.Editor)); ``` ```swift Swift v10 theme={null} try await client.userCollaborations.createCollaboration(requestBody: CreateCollaborationRequestBody(item: CreateCollaborationRequestBodyItemField(type: CreateCollaborationRequestBodyItemTypeField.folder, id: folder.id), accessibleBy: CreateCollaborationRequestBodyAccessibleByField(type: CreateCollaborationRequestBodyAccessibleByTypeField.user, id: user.id), role: CreateCollaborationRequestBodyRoleField.editor)) ``` ```java Java v10 theme={null} client.getUserCollaborations().createCollaboration(new CreateCollaborationRequestBody(new CreateCollaborationRequestBodyItemField.Builder().type(CreateCollaborationRequestBodyItemTypeField.FOLDER).id(folder.getId()).build(), new CreateCollaborationRequestBodyAccessibleByField.Builder(CreateCollaborationRequestBodyAccessibleByTypeField.USER).id(user.getId()).build(), CreateCollaborationRequestBodyRoleField.EDITOR)) ``` ```java Java v5 theme={null} BoxCollaborator user = new BoxUser(api, "user-id") BoxFolder folder = new BoxFolder(api, "folder-id"); folder.collaborate(user, BoxCollaboration.Role.EDITOR); ``` ```python Python v4 theme={null} from boxsdk.object.collaboration import CollaborationRole user = client.user(user_id='11111') collaboration = client.folder(folder_id='22222').collaborate(user, CollaborationRole.VIEWER) collaborator = collaboration.accessible_by item = collaboration.item has_accepted = 'has' if collaboration.status == 'accepted' else 'has not' print(f'{collaborator.name} {has_accepted} accepted the collaboration to folder "{item.name}"') ``` ```cs .NET v6 theme={null} // collaborate folder 11111 with user 22222 BoxCollaborationRequest requestParams = new BoxCollaborationRequest() { Item = new BoxRequestEntity() { Type = BoxType.folder, Id = "11111" }, Role = "editor", AccessibleBy = new BoxCollaborationUserRequest() { Type = BoxType.user, Id = "22222" } }; BoxCollaboration collab = await client.CollaborationsManager.AddCollaborationAsync(requestParams); ``` ```javascript Node v4 theme={null} // Invite user 123456 to collaborate on folder 987654 client.collaborations.createWithUserID('123456', '987654', client.collaborationRoles.EDITOR) .then(collaboration => { /* collaboration -> { type: 'collaboration', id: '11111', created_by: { type: 'user', id: '22222', name: 'Inviting User', login: 'inviter@example.com' }, created_at: '2016-11-16T21:33:31-08:00', modified_at: '2016-11-16T21:33:31-08:00', expires_at: null, status: 'accepted', accessible_by: { type: 'user', id: '123456', name: 'Collaborator User', login: 'collaborator@example.com' }, role: 'editor', acknowledged_at: '2016-11-16T21:33:31-08:00', item: { type: 'folder', id: '987654', sequence_id: '0', etag: '0', name: 'Collaborated Folder' } } */ }); ``` ### Fetch Collaboration ID as Transfer To User The next step is to make a request to get the collaboration information, making the request as the `transfer_to_user` account. The collaboration object returned will include a collaboration ID, which is used for the last step. Making the call as the `transfer_to_user` account, get the collaboration on the file or folder ID being transferred, using the get collaboration endpoint. Capture the collaboration ID. ```sh cURL theme={null} curl -i -X GET "https://api.box.com/2.0/collaborations/1234" \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.userCollaborations.getCollaborationById(collaborationId); ``` ```python Python v10 theme={null} client.user_collaborations.get_collaboration_by_id(collaboration_id) ``` ```cs .NET v10 theme={null} await client.UserCollaborations.GetCollaborationByIdAsync(collaborationId: collaborationId); ``` ```swift Swift v10 theme={null} try await client.userCollaborations.getCollaborationById(collaborationId: collaborationId) ``` ```java Java v10 theme={null} client.getUserCollaborations().getCollaborationById(collaborationId) ``` ```cs .NET v6 theme={null} await client.UserCollaborations.GetCollaborationByIdAsync(collaborationId: collaborationId); ``` ```javascript Node v4 theme={null} await client.userCollaborations.getCollaborationById(collaborationId); ``` ### Remove Transfer From User as Owner The final step is to remove the `transfer_from_user` account as an owner of the file or folder, which is accomplished using the delete collaboration endpoint. Making the call as the `transfer_to_user` account, remove the `transfer_from_user` as a collaborator on the file or folder. ```sh cURL theme={null} curl -i -X DELETE "https://api.box.com/2.0/collaborations/1234" \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.userCollaborations.deleteCollaborationById(collaborationId); ``` ```python Python v10 theme={null} client.user_collaborations.delete_collaboration_by_id(collaboration_id) ``` ```cs .NET v10 theme={null} await client.UserCollaborations.DeleteCollaborationByIdAsync(collaborationId: collaborationId); ``` ```swift Swift v10 theme={null} try await client.userCollaborations.deleteCollaborationById(collaborationId: collaborationId) ``` ```java Java v10 theme={null} client.getUserCollaborations().deleteCollaborationById(collaborationId) ``` ```cs .NET v6 theme={null} await client.UserCollaborations.DeleteCollaborationByIdAsync(collaborationId: collaborationId); ``` ```javascript Node v4 theme={null} await client.userCollaborations.getCollaborationById(collaborationId); ``` The file or folder is now owned by the `transfer_to_user` account, and the `transfer_from_user` account no longer has access. # Users overview Source: https://developer.box.com/guides/users/index The Box API supports a variety of users, ranging from real employees logging in with their Managed User account, to applications using App Users to drive powerful automation workflows. ## (De-)Provision Users Managing the onboarding and offboarding of employees, customers, and users is a common requirement in the lifespan of a Box application. During account provisioning the main tasks that will be needed will be: * How to create new app and managed user accounts to represent the users. * How to instantiate the new user account with common or repeatable folder and file architectures. During account deprovisioning the main tasks that will be needed will be: * How to transfer files and folders from one account to another for offboarding. * How to delete user accounts. # Create Architecture Skeleton Source: https://developer.box.com/guides/users/provision/architecture Our first requirement is to copy general files and folders into each individual user's root folder on account creation. This problem has been solved within standard Linux distributions through a directory called `/etc/skel`, which we'll emulate with a Box specific solution here. When adding a new user in Linux, the files and folders within `/etc/skel` are copied to the new user's home directory. When creating a JWT-based Box application, a Service Account is created within the Box Enterprise. A Service Account is similar in functionality to a co-admin within a Box Enterprise, and most useful to this use case, can own, copy, and collaborate other users on files and folders. More importantly, you don't have to use a Service Account strictly for developing platform applications for users, and instead, can use a Service Account in more of an automation capacity. **Platform app requirements** When creating your JWT-based custom Box application for this recipe, you'll need to enable the following scopes: **Manage users**, **Manage groups**, **Perform Actions as Users**, and **Generate User Access Tokens**. See JWT Application Setup for more information on creating a JWT-based Box application and the scopes in a Box application. We'll start by creating the `etc` and `skel` folders and granting ownership of the folders to the Service Account. ```json theme={null} { "name": "etc", "parent": { "id": "0" }, "children": [ { "name": "skel", "children": [] } ] } ``` ```json theme={null} [ { "name": "Market Research", "parent": { "id": "44884797174" }, "children": [ { "name": "Statistics", "children": [ { "name": "Computed", "children": [] } ] } ] }, { "name": "Sales Plays", "parent": { "id": "44884797174" }, "children": [ { "name": "Big Pharma", "children": [] } ] } ] ``` The code here can optionally be reused to build any folder structure formatted as the JSON objects above demonstrate. ```js theme={null} "use strict"; const fs = require("fs"); const box = require("box-node-sdk"); class BoxFolderTreeCreator { constructor(boxClient, options) { options = options || {}; if (options.boxClient) { throw new Error("Must include a boxClient field."); } options.boxFolderTreeName = options.boxFolderTreeName || "tree.json"; this.boxClient = boxClient; this.boxFolderTree = JSON.parse(fs.readFileSync(options.boxFolderTreeName)); this.createdBoxFolders = []; } async createFolderTree(branch = null, parentFolderId = "0") { this.createdBoxFolders = []; if (Array.isArray(this.boxFolderTree)) { let folderTasks = []; this.boxFolderTree.forEach(folder => { folderTasks.push(this._createFolder(folder, "")); }); await Promise.all(folderTasks); return this.createdBoxFolders; } else if (typeof this.boxFolderTree === "object") { console.log("Is object"); await this._createFolders(this.boxFolderTree, ""); return this.createdBoxFolders; } else { throw new Error("Incorrectly formatted JSON folder tree."); } } async _createFolders(branch, parentFolderId = "0") { if (branch.parent != null && branch.parent.id != null) { parentFolderId = branch.parent.id; } let folder; try { folder = await this.boxClient.folders.create(parentFolderId, branch.name); } catch (e) { let existingFolderId = BoxFolderTreeCreator.handleFolderConflictError(e); folder = await this.boxClient.folders.get(existingFolderId); } this.createdBoxFolders.push(folder); if (branch.children.length <= 0) { console.log("No more folders to create..."); return; } else { let createFolderTasks = []; branch.children.forEach(child => { console.log("Creating folder..."); console.log(child.name); createFolderTasks.push(this._createFolders(child, folder.id)); }); return await Promise.all(createFolderTasks); } } static handleFolderConflictError(e) { if (e && e.response && e.response.body) { let errorBody = e.response.body; if (errorBody.status === 409) { if ( errorBody.context_info && errorBody.context_info.conflicts && errorBody.context_info.conflicts.length > 0 ) { let conflict = errorBody.context_info.conflicts[0]; if (conflict && conflict.id) { return conflict.id; } } } } } } let configFile = fs.readFileSync("config.json"); configFile = JSON.parse(configFile); let session = box.getPreconfiguredInstance(configFile); let serviceAccountClient = session.getAppAuthClient("enterprise"); let treeCreator = new BoxFolderTreeCreator(serviceAccountClient); (async () => { let createdFolders = await treeCreator.createFolderTree(); console.log(createdFolders); })(); ``` ```java theme={null} package com.box; import java.io.BufferedReader; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.box.sdk.BoxAPIException; import com.box.sdk.BoxConfig; import com.box.sdk.BoxDeveloperEditionAPIConnection; import com.box.sdk.BoxFolder; import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.JsonValue; public class BoxFolderTreeCreator { private BoxDeveloperEditionAPIConnection _boxClient; private JsonValue _boxFolderTree; private ArrayList _createdFolders; public BoxFolderTreeCreator(BoxDeveloperEditionAPIConnection boxClient) throws IOException { this(boxClient, "tree.json"); } public BoxFolderTreeCreator(BoxDeveloperEditionAPIConnection boxClient, String folderTreeFileName) throws IOException { this._boxClient = boxClient; try (BufferedReader tree = Files.newBufferedReader(Paths.get(folderTreeFileName))) { this._boxFolderTree = JsonValue.readFrom(tree); } this._createdFolders = new ArrayList<>(); } public ArrayList createFolderTree() { if (this._boxFolderTree.isArray()) { for (JsonValue folder: this._boxFolderTree.asArray()) { System.out.println("Processing this folder: " + folder); _createFolders(folder.asObject(), null); } return this._createdFolders; } else { _createFolders(this._boxFolderTree.asObject(), null); return this._createdFolders; } } private void _createFolders(JsonObject branch, String parentFolderID) { if (parentFolderID == null && branch.get("parent") != null && branch.get("parent").asObject().get("id") != null) { System.out.println("Looking for parent folder id..."); System.out.println(branch.get("parent").asObject().get("id").asString()); parentFolderID = branch.get("parent").asObject().get("id").asString(); } System.out.println("Folder name:"); System.out.println(branch.get("name")); System.out.println("Parent Folder ID:"); System.out.println(parentFolderID); BoxFolder.Info createdFolder; try { BoxFolder parent = new BoxFolder(this._boxClient, parentFolderID); createdFolder = parent.createFolder(branch.get("name").asString()); } catch (BoxAPIException e) { if (e.getResponseCode() == 409) { // Use the ID returned from the conflict error to continue String conflictId = getIdFromConflict(e.getResponse()); createdFolder = new BoxFolder(this._boxClient, conflictId).getInfo(); } else { throw e; } } this._createdFolders.add(createdFolder); if (!branch.get("children").asArray().isEmpty()) { for (JsonValue child: branch.get("children").asArray()) { _createFolders(child.asObject(), createdFolder.getID()); } } else { return; } } private static String getIdFromConflict(String message) { String id = ""; Pattern p = Pattern.compile("\"id\":\"[0-9]+\""); Pattern p2 = Pattern.compile("[0-9]+"); Matcher m = p.matcher(message); if (m.find()) { String sub = m.group(); Matcher m2 = p2.matcher(sub); if (m2.find()) { id = m2.group(); } } return id; } public static void main(String[] args) throws Exception { Path configPath = Paths.get("config.json"); try (BufferedReader reader = Files.newBufferedReader(configPath, Charset.forName("UTF-8"))) { BoxConfig boxConfig = BoxConfig.readFrom(reader); BoxDeveloperEditionAPIConnection serviceAccountClient = BoxDeveloperEditionAPIConnection .getAppEnterpriseConnection(boxConfig); BoxFolderTreeCreator treeBuilder = new BoxFolderTreeCreator(serviceAccountClient, "etc_skel.json"); ArrayList folders = treeBuilder.createFolderTree(); for (BoxFolder.Info folder: folders) { System.out.println(folder.getID()); } } } } ``` ```csharp theme={null} using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Box.V2; using Box.V2.Config; using Box.V2.Exceptions; using Box.V2.JWTAuth; using Box.V2.Models; using Newtonsoft.Json.Linq; namespace BoxPlayground { public class Program { static void Main(string[] args) { ExecuteMainAsync().Wait(); } private static async Task ExecuteMainAsync() { using(FileStream fs = new FileStream("./config.json", FileMode.Open)) { var session = new BoxJWTAuth(BoxConfig.CreateFromJsonFile(fs)); var client = session.AdminClient(session.AdminToken()); var treeCreator = new BoxFolderTreeCreator(client, "etc_skel.json"); var createdFolders = await treeCreator.CreateFolderTree(); foreach(var folder in createdFolders) { System.Console.WriteLine(folder.Name); System.Console.WriteLine(folder.Id); } } } public class BoxFolderTreeCreator { public BoxClient BoxClient { get; set; } public JToken BoxFolderTree { get; set; } public List < BoxFolder > CreatedBoxFolders { get; set; } public BoxFolderTreeCreator(BoxClient boxClient, string boxFolderTreeFileName = "tree.json") { this.BoxClient = boxClient; this.BoxFolderTree = JToken.Parse(File.ReadAllText(boxFolderTreeFileName)); this.CreatedBoxFolders = new List < BoxFolder > (); } public async Task < List < BoxFolder >> CreateFolderTree(dynamic branch = null, string parentFolderId = "0") { this.CreatedBoxFolders = new List < BoxFolder > (); if (this.BoxFolderTree is JArray) { var folderTasks = new List < Task > (); foreach(JObject folder in this.BoxFolderTree) { folderTasks.Add(_createFolder(folder, String.Empty)); } await Task.WhenAll(folderTasks); return this.CreatedBoxFolders; } else if (this.BoxFolderTree is JObject) { System.Console.WriteLine("Is object"); await _createFolder(this.BoxFolderTree as JObject, String.Empty); return this.CreatedBoxFolders; } else { throw new Exception("Incorrectly formatted JSON folder tree."); } } private async Task _createFolder(dynamic branch, string parentFolderId = "0") { if (branch.parent != null && branch.parent.id != null) { parentFolderId = branch.parent.id; } BoxFolder createdFolder; try { createdFolder = await this.BoxClient.FoldersManager.CreateAsync( new BoxFolderRequest { Parent = new BoxRequestEntity { Id = parentFolderId }, Name = branch.name }); } catch(BoxConflictException < BoxFolder > e) { createdFolder = await this.BoxClient.FoldersManager.GetInformationAsync(e.ConflictingItems.FirstOrDefault().Id); } this.CreatedBoxFolders.Add(createdFolder); if (branch.children.Count <= 0) { System.Console.WriteLine("No more folders to create..."); return; } else { var createFolderTasks = new List < Task > (); foreach(var child in branch.children) { System.Console.WriteLine("Creating folder..."); System.Console.WriteLine(child.name); createFolderTasks.Add(_createFolder(child as JObject, createdFolder.Id)); } await Task.WhenAll(createFolderTasks); } } } } } ``` # Provision Users Source: https://developer.box.com/guides/users/provision/index When setting up a brand new Box user account, a common need is to have that new account be populated with standard folders, collaborations, and group associations. Typically there are some common questions that may be asked about the user account to determine when standard setup may be needed for the account: * Will the user need access to standard company files or folders immediately? * Are collaborations associated individually or through groups? If through a group association, are there any standard groups that the user should be added to? * Should the user be assigned any tasks to complete? * Would any instructional comments on any files be helpful? **New User Password Resets and Email Confirmation** You may experience some errors when creating users and immediately trying to take actions with that user through the API. For example, a common error to receive is `user_email_confirmation_required` or `password_reset_required`. These kinds of errors may block some actions within the API, but you can still add the user as a collaborator on folders, add the user to groups, etc. ## Sample Overview In this scenario we'll focus on provisioning a new Box Managed User, as there are very different considerations when provisioning Box App User accounts. We'll start with solving the most repeatable aspects of provisioning a user's account, creating a general folder and file structure that everyone will have on first login, using groups to control access to shared files and folders for users. To create the managed user account itself, see create managed user. ## Guide steps This guide walks through the provisioning process in three steps: Create an `etc/skel` folder structure owned by your application's service account to hold the default files and folders for every new user. Copy the skeleton content into a new user's root folder when the account is created. Create shared folders in the service account and use groups to control each user's level of access. # Populate Content Source: https://developer.box.com/guides/users/provision/populate-content Once the architecture files have been defined through the `etc/skel` structure in your service account, you can now use the following script to copy anything under the `skel` directly to the new user's root directory. ```js theme={null} 'use strict' const box = require('box-node-sdk'); const fs = require('fs'); const skelFolderId = "45117847998"; const userID = "275111793"; let configFile = fs.readFileSync('config.json'); configFile = JSON.parse(configFile); let session = box.getPreconfiguredInstance(configFile); let serviceAccountClient = session.getAppAuthClient("enterprise"); (async () => { // The userID can be obtained when creating the user via the // API or by using the search users feature. // The skel folder ID shouldn't ever change unless it's deleted and recreated. await copySkelDirectoryForUser(userID, skelFolderId, serviceAccountClient); })(); async function copySkelDirectoryForUser(userID, skelFolderId, boxClient) { // Enable iterators in case there are more than the // default limit of items under the skel directory. boxClient._useIterators = true; // You collaborate the user temporarily on the skel directory // to copy all items into that user's root folder. let collabSkelFolder; try { collabSkelFolder = await boxClient.collaborations.createWithUserID(userID, skelFolderId, boxClient.collaborationRoles.EDITOR); } catch (e) { // Handle that the collaboration on the skel folder could already exist. if (e.response.body.code === 'user_already_collaborator') { let collaborationsIterator = await boxClient.folders.getCollaborations(skelFolderId); let collaborations = await autoPage(collaborationsIterator); let results = collaborations.filter((collaboration) => { return collaboration.accessible_by.id === userID; }); console.log(results); if (results.length > 0) { collabSkelFolder = results[0]; } else { throw new Error("Couldn't create new collaboration or located existing collaboration."); } } else { throw e; } } console.log(collabSkelFolder); // Switching context to make calls on behalf of the user. // To access this user's root folder, the boxClient needs // to be scoped to make API calls as the user. boxClient.asUser(userID); // Iterate over all the items under the skel directory. let skelFolderItemsIterator = await boxClient.folders.getItems(skelFolderId); let skelFolderCollection = await autoPage(skelFolderItemsIterator); console.log(skelFolderCollection); // Now, as the user, copy the folders and files into // the user's root folder -- folder ID '0'. let copyTasks = []; skelFolderCollection.forEach((item) => { if (item.type === 'folder') { copyTasks.push(boxClient.folders.copy(item.id, '0') .catch((e) => { let itemId = handleConflictError(e); if (itemId) { console.log(itemId); return boxClient.folders.get(itemId); } else { throw e; } })); } else if (item.type === 'file') { copyTasks.push(boxClient.files.copy(item.id, '0') .catch((e) => { let itemId = handleConflictError(e); if (itemId) { console.log(itemId); return boxClient.files.get(itemId); } else { throw e; } })); } else { console.log("Unable to resolve item type to known types..."); } }); let copiedItems = await Promise.all(copyTasks); console.log(copiedItems); // Switching the boxClient context back to that of the service account. boxClient.asSelf(); /* Since the service account owns the skel directory, boxClient needs to make API calls as the service account to remove the temporary collaboration on the skel directory. */ try { await boxClient.collaborations.delete(collabSkelFolder.id); console.log("Removed collaboration on skel..."); } catch (e) { console.log("Couldn't remove skel collaboration..."); console.log(e.respose.body); } function handleConflictError(e) { if (e && e.response && e.response.body) { let errorBody = e.response.body; if (errorBody.status === 409) { if (errorBody.context_info && errorBody.context_info.conflicts && errorBody.context_info.conflicts) { let conflict = errorBody.context_info.conflicts; if (conflict && conflict.id) { return conflict.id; } } } } } function autoPage(iterator, collection = []) { let moveToNextItem = async () => { let item = await iterator.next(); if (item.value) { collection.push(item.value); } if (item.done !== true) { return moveToNextItem(); } else { return collection; } } return moveToNextItem(); } } ``` ```java theme={null} package com.box; import java.io.BufferedReader; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.box.sdk.BoxAPIException; import com.box.sdk.BoxCollaboration; import com.box.sdk.BoxConfig; import com.box.sdk.BoxDeveloperEditionAPIConnection; import com.box.sdk.BoxFile; import com.box.sdk.BoxFolder; import com.box.sdk.BoxItem; import com.box.sdk.BoxUser; import com.eclipsesource.json.JsonObject; public class BoxPlayground { public static void main(String[] args) throws Exception { Path configPath = Paths.get("config.json"); try (BufferedReader reader = Files.newBufferedReader(configPath, Charset.forName("UTF-8"))) { String skelFolderId = "45117847998"; String userId = "275111793"; BoxConfig boxConfig = BoxConfig.readFrom(reader); BoxDeveloperEditionAPIConnection serviceAccountClient = BoxDeveloperEditionAPIConnection .getAppEnterpriseConnection(boxConfig); BoxDeveloperEditionAPIConnection userClient = BoxDeveloperEditionAPIConnection.getAppUserConnection(userId, boxConfig); BoxFolder skelFolder = new BoxFolder(serviceAccountClient, skelFolderId); BoxCollaboration.Info skelFolderCollaboration; try { skelFolderCollaboration = skelFolder.collaborate(new BoxUser(serviceAccountClient, userId), BoxCollaboration.Role.EDITOR); } catch (BoxAPIException e) { System.out.println("Searching for existing collaborator."); JsonObject errorMessage = JsonObject.readFrom(e.getResponse()); String code = errorMessage.get("code").asString().intern(); if (code == "user_already_collaborator") { System.out.println("Already collaborated..."); Collection collaborations = skelFolder.getCollaborations(); System.out.println(collaborations.size()); Optional results = collaborations.stream().filter(c -> { return c.getAccessibleBy().getID().intern() == userId; }).findFirst(); if (results.isPresent()) { skelFolderCollaboration = results.get(); } else { throw new Exception("Couldn't create new collaboration or find existing collaboration."); } } else { throw e; } } System.out.println(skelFolderCollaboration.getID()); BoxFolder collabedSkelFolder = new BoxFolder(userClient, skelFolderId); ArrayList copiedItems = new ArrayList<>(); for (BoxItem.Info itemInfo: collabedSkelFolder) { if (itemInfo instanceof BoxFile.Info) { BoxFile.Info fileInfo = (BoxFile.Info) itemInfo; BoxFile copyFile = new BoxFile(userClient, fileInfo.getID()); BoxFile.Info copiedFile; try { copiedFile = copyFile.copy(BoxFolder.getRootFolder(userClient)); } catch (BoxAPIException e) { System.out.println(e.getMessage()); String conflictId = getIdFromConflict(e.getMessage()); System.out.println(conflictId); copiedFile = new BoxFile(userClient, conflictId).getInfo(); } copiedItems.add((BoxItem.Info) copiedFile); } else if (itemInfo instanceof BoxFolder.Info) { BoxFolder.Info folderInfo = (BoxFolder.Info) itemInfo; BoxFolder copyFolder = new BoxFolder(userClient, folderInfo.getID()); BoxFolder.Info copiedFolder; try { copiedFolder = copyFolder.copy(BoxFolder.getRootFolder(userClient)); } catch (BoxAPIException e) { System.out.println(e.getMessage()); String conflictId = getIdFromConflict(e.getMessage()); System.out.println(conflictId); copiedFolder = new BoxFolder(userClient, conflictId).getInfo(); } copiedItems.add((BoxItem.Info) copiedFolder); } } System.out.println("Copied " + copiedItems.size() + " items from the skel directory."); BoxCollaboration tempSkelCollab = new BoxCollaboration(serviceAccountClient, skelFolderCollaboration.getID()); tempSkelCollab.delete(); System.out.println("Removed temporary skel directory collaboration."); } } private static String getIdFromConflict(String message) { String id = ""; Pattern p = Pattern.compile("\"id\":\"[0-9]+\""); Pattern p2 = Pattern.compile("[0-9]+"); Matcher m = p.matcher(message); if (m.find()) { String sub = m.group(); Matcher m2 = p2.matcher(sub); if (m2.find()) { id = m2.group(); } } return id; } } ``` ```csharp theme={null} using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Box.V2; using Box.V2.Config; using Box.V2.Exceptions; using Box.V2.JWTAuth; using Box.V2.Models; using Newtonsoft.Json.Linq; namespace BoxPlayground { public class Program { static void Main(string[] args) { ExecuteMainAsync().Wait(); } private static async Task ExecuteMainAsync() { using(FileStream fs = new FileStream("./config.json", FileMode.Open)) { var skelFolderId = "45117847998"; var userId = "275111793"; var session = new BoxJWTAuth(BoxConfig.CreateFromJsonFile(fs)); var client = session.AdminClient(session.AdminToken()); var userClient = session.UserClient(session.UserToken(userId), userId); BoxCollaboration collabSkelFolder; try { collabSkelFolder = await client.CollaborationsManager.AddCollaborationAsync( new BoxCollaborationRequest { AccessibleBy = new BoxCollaborationUserRequest { Id = userId }, Item = new BoxRequestEntity { Id = skelFolderId, Type = BoxType.folder }, Role = BoxCollaborationRole.Editor.ToString() }); } catch(BoxException e) { var errorMessage = JObject.Parse(e.Message); if (errorMessage.GetValue("code").ToString() == "user_already_collaborator") { System.Console.WriteLine("Already a collaborator"); var collaborations = await client.FoldersManager.GetCollaborationsAsync(skelFolderId); var existingCollab = collaborations.Entries.Find((collaboration) = >{ return collaboration.AccessibleBy.Id == userId; }); if (existingCollab != null) { collabSkelFolder = existingCollab; } else { throw new Exception("Couldn't create new collaboration or find existing collaboration"); } } else { throw e; } } var items = await userClient.FoldersManager.GetFolderItemsAsync(skelFolderId, limit: 1000, autoPaginate: true); var copyTasks = new List < Task < BoxItem >> (); items.Entries.ForEach((item) = >{ if (item.Type == BoxType.folder.ToString()) { copyTasks.Add(userClient.FoldersManager.CopyAsync(new BoxFolderRequest { Id = item.Id, Parent = new BoxRequestEntity { Id = "0" } }).ContinueWith((folder) = >{ try { return (BoxItem) folder.Result; } catch(Exception e) { var errorMessage = JObject.Parse(e.InnerException.Message); if (errorMessage.GetValue("status").ToObject < int > () == 409) { System.Console.WriteLine("Conflict found"); System.Console.WriteLine(errorMessage.SelectToken("context_info.conflicts.id")); return (BoxItem) userClient.FoldersManager.GetInformationAsync(errorMessage.SelectToken("context_info.conflicts.id").ToString()).Result; } else { throw e; } } })); } else if (item.Type == BoxType.file.ToString()) { copyTasks.Add(userClient.FilesManager.CopyAsync(new BoxFileRequest { Id = item.Id, Parent = new BoxRequestEntity { Id = "0" } }).ContinueWith((file) = >{ try { return (BoxItem) file.Result; } catch(Exception e) { var errorMessage = JObject.Parse(e.InnerException.Message); if (errorMessage.GetValue("status").ToObject < int > () == 409) { System.Console.WriteLine("Conflict found"); System.Console.WriteLine(errorMessage.SelectToken("context_info.conflicts.id")); return (BoxItem) userClient.FilesManager.GetInformationAsync(errorMessage.SelectToken("context_info.conflicts.id").ToString()).Result; } else { throw e; } } })); } else { System.Console.WriteLine("Couldn't process this item..."); } }); var copiedItems = await Task.WhenAll(copyTasks); System.Console.WriteLine($ "Copied {copiedItems.Count()} items from the skel directory."); if (await client.CollaborationsManager.RemoveCollaborationAsync(collabSkelFolder.Id)){ System.Console.WriteLine("Removed temporary skel directory collaboration..."); System.Console.WriteLine("Complete!"); } else { System.Console.WriteLine("Something went wrong when removing skel directory collaboration."); } } } } } ``` # Setup Shared Folders Source: https://developer.box.com/guides/users/provision/shared-folders As a final step to manage access to shared folders, we'll create the folder structures needed within the service account. Then, groups will map to the needed permissions based on user types and level of access needed to those folders. We'll use a Marketing Department as an example. ```json theme={null} { "name": "Marketing Department", "parent": { "id": "0" }, "children": [ { "name": "Projects", "children": [] }, { "name": "Newsletter", "children": [ { "name": "Drafts", "children": [] } ] } ] } ``` Working from this sample folder structure, we can use the folder tree creator code we used earlier to create the `etc/skel` structure. That code may be modified to make your own structure. Once created, we'll need the IDs of the folders that each group will need to access. For example, Marketing managers will likely have `editor` access to all folders within the Marketing Department. On the other hand, Marketing project managers will likely need `editor` access to only the `Projects` folder. We'll create two groups and give them these permissions. ```js theme={null} "use strict"; const fs = require("fs"); const box = require("box-node-sdk"); let configFile = fs.readFileSync("config.json"); configFile = JSON.parse(configFile); let session = box.getPreconfiguredInstance(configFile); let serviceAccountClient = session.getAppAuthClient("enterprise"); const marketingDeptFolderID = "45765309069"; const marketingProjectsFolderID = "45765461670"; const marketingManagersGroupName = "Marketing Managers"; const marketingProjectManagersGroupName = "Marketing Project Managers"; (async () => { let marketingManagerGroup; try { marketingManagerGroup = await serviceAccountClient.groups.create( marketingManagersGroupName, { description: "For Marketing department leadership team.", invitability_level: "admins_only", member_viewability_level: "admins_only" } ); } catch (e) { marketingManagerGroup = await handleGroupConflictError( e, marketingManagersGroupName, serviceAccountClient ); } console.log(marketingManagerGroup); let marketingProjectManagerGroup; try { marketingProjectManagerGroup = await serviceAccountClient.groups.create( marketingProjectManagersGroupName, { description: "All team members who manage Marketing projects.", invitability_level: "admins_and_members", member_viewability_level: "admins_and_members" } ); } catch (e) { marketingProjectManagerGroup = await handleGroupConflictError( e, marketingProjectManagersGroupName, serviceAccountClient ); } console.log(marketingProjectManagerGroup); let collabMarketingManagers; try { collabMarketingManagers = await serviceAccountClient.collaborations.createWithGroupID( marketingManagerGroup.id, marketingDeptFolderID, serviceAccountClient.collaborationRoles.EDITOR ); } catch (e) { collabMarketingManagers = await handleFolderCollaborationConflictError( e, marketingDeptFolderID, marketingManagerGroup.id, serviceAccountClient ); } console.log(collabMarketingManagers); let collabMarketingProjectManagers; try { collabMarketingProjectManagers = await serviceAccountClient.collaborations.createWithGroupID( marketingProjectManagerGroup.id, marketingProjectsFolderID, serviceAccountClient.collaborationRoles.EDITOR ); } catch (e) { collabMarketingProjectManagers = await handleFolderCollaborationConflictError( e, marketingProjectsFolderID, marketingProjectManagerGroup.id, serviceAccountClient ); } console.log(collabMarketingProjectManagers); })(); async function autoPage(iterator, collection = []) { let moveToNextItem = async () => { let item = await iterator.next(); if (item.value) { collection.push(item.value); } if (item.done !== true) { return moveToNextItem(); } else { return collection; } }; return moveToNextItem(); } async function handleGroupConflictError(e, groupName, boxClient) { let storeIteratorSetting = boxClient._useIterators; if (e && e.response && e.response.body && e.response.body.status === 409) { boxClient._useIterators = true; let groupsIterator = await boxClient.groups.getAll({ name: groupName }); let groups = await autoPage(groupsIterator); let results = groups.filter(group => { return group.name === groupName; }); if (results.length > 0) { boxClient._useIterators = storeIteratorSetting; return results[0]; } else { throw new Error("Couldn't create group or find existing group."); } } else { throw e; } } async function handleFolderCollaborationConflictError( e, folderID, groupID, boxClient ) { let storeIteratorSetting = boxClient._useIterators; if (e && e.response && e.response.body && e.response.body.status === 409) { boxClient._useIterators = true; let collaborationsIterator = await boxClient.folders.getCollaborations( folderID ); let collaborations = await autoPage(collaborationsIterator); let results = collaborations.filter(collaboration => { return collaboration.accessible_by.id === groupID; }); console.log(results); if (results.length > 0) { boxClient._useIterators = storeIteratorSetting; return results[0]; } else { throw new Error( "Couldn't create new collaboration or located existing collaboration." ); } } else { throw e; } } ``` ```java theme={null} package com.box; import java.io.BufferedReader; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; import java.util.Optional; import com.box.sdk.BoxAPIException; import com.box.sdk.BoxCollaboration; import com.box.sdk.BoxConfig; import com.box.sdk.BoxDeveloperEditionAPIConnection; import com.box.sdk.BoxFolder; import com.box.sdk.BoxGroup; import com.eclipsesource.json.JsonObject; public class BoxPlayground { public static void main(String[] args) throws Exception { Path configPath = Paths.get("config.json"); try (BufferedReader reader = Files.newBufferedReader(configPath, Charset.forName("UTF-8"))) { String marketingDeptFolderID = "45765309069"; String marketingProjectsFolderID = "45765461670"; String marketingManagersGroupName = "Marketing Managers"; String marketingProjectManagersGroupName = "Marketing Project Managers"; BoxConfig boxConfig = BoxConfig.readFrom(reader); BoxDeveloperEditionAPIConnection serviceAccountClient = BoxDeveloperEditionAPIConnection .getAppEnterpriseConnection(boxConfig); BoxGroup.Info marketingManagerGroup; try { marketingManagerGroup = BoxGroup.createGroup(serviceAccountClient, marketingManagersGroupName, null, null, "For Marketing department leadership team.", "admins_only", "admins_only"); } catch (BoxAPIException e) { JsonObject errorMessage = JsonObject.readFrom(e.getResponse()); int status = errorMessage.get("status").asInt(); if (status == 409) { marketingManagerGroup = handleGroupConflictError(marketingManagersGroupName, serviceAccountClient); } else { throw e; } } System.out.println(marketingManagerGroup.getID()); BoxGroup.Info marketingProjectManagerGroup; try { marketingProjectManagerGroup = BoxGroup.createGroup(serviceAccountClient, marketingProjectManagersGroupName, null, null, "All team members who manage Marketing projects.", "admins_and_members", "admins_and_members"); } catch (BoxAPIException e) { JsonObject errorMessage = JsonObject.readFrom(e.getResponse()); int status = errorMessage.get("status").asInt(); if (status == 409) { marketingProjectManagerGroup = handleGroupConflictError(marketingProjectManagersGroupName, serviceAccountClient); } else { throw e; } } System.out.println(marketingProjectManagerGroup.getID()); BoxFolder marketDeptFolder = new BoxFolder(serviceAccountClient, marketingDeptFolderID); BoxCollaboration.Info marketingDeptFolderCollaboration; try { marketingDeptFolderCollaboration = marketDeptFolder.collaborate( new BoxGroup(serviceAccountClient, marketingManagerGroup.getID()), BoxCollaboration.Role.EDITOR); } catch (BoxAPIException e) { System.out.println("Searching for existing collaborator."); JsonObject errorMessage = JsonObject.readFrom(e.getResponse()); int status = errorMessage.get("status").asInt(); if (status == 409) { marketingDeptFolderCollaboration = handleFolderCollaborationConflict(marketDeptFolder, marketingManagerGroup.getID()); } else { throw e; } } System.out.println(marketingDeptFolderCollaboration.getID()); BoxFolder marketingProjectsFolder = new BoxFolder(serviceAccountClient, marketingProjectsFolderID); BoxCollaboration.Info marketingProjectsFolderCollaboration; try { marketingProjectsFolderCollaboration = marketingProjectsFolder.collaborate( new BoxGroup(serviceAccountClient, marketingProjectManagerGroup.getID()), BoxCollaboration.Role.EDITOR); } catch (BoxAPIException e) { System.out.println("Searching for existing collaborator."); JsonObject errorMessage = JsonObject.readFrom(e.getResponse()); int status = errorMessage.get("status").asInt(); if (status == 409) { marketingProjectsFolderCollaboration = handleFolderCollaborationConflict(marketingProjectsFolder, marketingProjectManagerGroup.getID()); } else { throw e; } } System.out.println(marketingProjectsFolderCollaboration.getID()); } } private static BoxCollaboration.Info handleFolderCollaborationConflict(BoxFolder folder, String groupID) throws Exception { System.out.println("Already collaborated..."); Collection collaborations = folder.getCollaborations(); Optional results = collaborations.stream().filter(c -> { return c.getAccessibleBy().getID().intern() == groupID.intern(); }).findFirst(); if (results.isPresent()) { return results.get(); } else { throw new Exception("Couldn't create new collaboration or find existing collaboration."); } } private static BoxGroup.Info handleGroupConflictError(String groupName, BoxDeveloperEditionAPIConnection boxClient) throws Exception { Iterable groups = BoxGroup.getAllGroupsByName(boxClient, groupName); BoxGroup.Info foundGroup = null; for (BoxGroup.Info group: groups) { if (group.getName().intern() == groupName) { foundGroup = group; break; } } if (foundGroup != null) { return foundGroup; } else { throw new Exception("Couldn't create group or find existing group."); } } } ``` ```csharp theme={null} using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Box.V2; using Box.V2.Config; using Box.V2.Exceptions; using Box.V2.JWTAuth; using Box.V2.Models; using Box.V2.Models.Request; using Newtonsoft.Json.Linq; namespace BoxPlayground { public class Program { static void Main(string[] args) { ExecuteMainAsync().Wait(); } private static async Task ExecuteMainAsync() { using(FileStream fs = new FileStream("./config.json", FileMode.Open)) { var marketingDeptFolderId = "45765309069"; var marketingProjectsFolderId = "45765461670"; var marketingManagersGroupName = "Marketing Managers"; var marketingProjectManagersGroupName = "Marketing Project Managers"; var session = new BoxJWTAuth(BoxConfig.CreateFromJsonFile(fs)); var serviceAccountClient = session.AdminClient(session.AdminToken()); BoxGroup marketingManagerGroup; try { marketingManagerGroup = await serviceAccountClient.GroupsManager.CreateAsync(new BoxGroupRequest { Name = marketingManagersGroupName, InvitabilityLevel = "admins_only", MemberViewabilityLevel = "admins_only" }); } catch(BoxException e) { var errorMessage = JObject.Parse(e.Message); if (errorMessage.GetValue("status").ToObject < int > () == 409) { marketingManagerGroup = await HandleGroupConflictError(marketingManagersGroupName, serviceAccountClient); } else { throw e; } } System.Console.WriteLine(marketingManagerGroup.Id); BoxGroup marketingProjectsManagerGroup; try { marketingProjectsManagerGroup = await serviceAccountClient.GroupsManager.CreateAsync(new BoxGroupRequest { Name = marketingProjectManagersGroupName, InvitabilityLevel = "admins_and_members", MemberViewabilityLevel = "admins_and_members" }); } catch(BoxException e) { var errorMessage = JObject.Parse(e.Message); if (errorMessage.GetValue("status").ToObject < int > () == 409) { marketingProjectsManagerGroup = await HandleGroupConflictError(marketingProjectManagersGroupName, serviceAccountClient); } else { throw e; } } System.Console.WriteLine(marketingProjectsManagerGroup.Id); BoxCollaboration marketingManagerCollab; try { marketingManagerCollab = await serviceAccountClient.CollaborationsManager.AddCollaborationAsync( new BoxCollaborationRequest { AccessibleBy = new BoxCollaborationUserRequest { Id = marketingManagerGroup.Id, Type = BoxType.group }, Item = new BoxRequestEntity { Id = marketingDeptFolderId, Type = BoxType.folder }, Role = BoxCollaborationRole.Editor.ToString() }); } catch(BoxException e) { var errorMessage = JObject.Parse(e.Message); if (errorMessage.GetValue("status").ToObject < int > () == 409) { marketingManagerCollab = await HandleFolderCollaborationConflictError(marketingDeptFolderId, marketingManagerGroup.Id, serviceAccountClient); } else { throw e; } } System.Console.WriteLine(marketingManagerCollab.Id); BoxCollaboration marketingProjectsManagerCollab; try { marketingProjectsManagerCollab = await serviceAccountClient.CollaborationsManager.AddCollaborationAsync( new BoxCollaborationRequest { AccessibleBy = new BoxCollaborationUserRequest { Id = marketingProjectsManagerGroup.Id, Type = BoxType.group }, Item = new BoxRequestEntity { Id = marketingProjectsFolderId, Type = BoxType.folder }, Role = BoxCollaborationRole.Editor.ToString() }); } catch(BoxException e) { var errorMessage = JObject.Parse(e.Message); if (errorMessage.GetValue("status").ToObject < int > () == 409) { marketingProjectsManagerCollab = await HandleFolderCollaborationConflictError(marketingProjectsFolderId, marketingProjectsManagerGroup.Id, serviceAccountClient); } else { throw e; } } System.Console.WriteLine(marketingProjectsManagerCollab.Id); } } public async static Task < BoxGroup > HandleGroupConflictError(string groupName, BoxClient boxClient) { System.Console.WriteLine("Found conflict."); var groups = await boxClient.GroupsManager.GetAllGroupsAsync(autoPaginate: true); return groups.Entries.Find((group) = >{ return group.Name == groupName; }); } public async static Task < BoxCollaboration > HandleFolderCollaborationConflictError(string folderId, string groupId, BoxClient boxClient) { System.Console.WriteLine("Already a collaborator"); var collaborations = await boxClient.FoldersManager.GetCollaborationsAsync(folderId); var existingCollab = collaborations.Entries.Find((collaboration) = >{ return collaboration.AccessibleBy.Id == groupId; }); if (existingCollab != null) { return existingCollab; } else { throw new Exception("Couldn't create new collaboration or find existing collaboration"); } } } } ``` ```shell theme={null} box groups:create "Marketing Managers" --invite=admins_only --view-members=admins_only box groups:create "Marketing Project Managers" --invite=admins_and_members --view-members=admins_and_members ``` Once the groups are created, add the user to that group and they will have the prescribed access to the shared folders created within the service account. ```js theme={null} 'use strict' const fs = require('fs'); const box = require('box-node-sdk'); let configFile = fs.readFileSync('config.json'); configFile = JSON.parse(configFile); let session = box.getPreconfiguredInstance(configFile); let serviceAccountClient = session.getAppAuthClient("enterprise"); const marketingManagerGroupID = "839790214"; const marketingManagerUserID = "275111793"; (async () => { let addedUser; try { addedUser = await serviceAccountClient.groups.addUser(marketingManagerGroupID, marketingManagerUserID, { role: serviceAccountClient.groups.userRoles.ADMIN }); } catch (e) { addedUser = await handleGroupMembershipConflictError(e, marketingManagerGroupID, marketingManagerUserID, serviceAccountClient); } console.log(addedUser); })(); async function autoPage(iterator, collection = []) { let moveToNextItem = async () => { let item = await iterator.next(); if (item.value) { collection.push(item.value); } if (item.done !== true) { return moveToNextItem(); } else { return collection; } } return moveToNextItem(); } async function handleGroupMembershipConflictError(e, groupID, userID, boxClient) { let storeIteratorSetting = boxClient._useIterators; if (e && e.response && e.response.body && e.response.body.status === 409) { boxClient._useIterators = true; let groupMembershipsIterator = await boxClient.groups.getMemberships(groupID); let groupMemberships = await autoPage(groupMembershipsIterator); let results = groupMemberships.filter((groupMembership) => { return groupMembership.user.id === userID; }); if (results.length > 0) { boxClient._useIterators = storeIteratorSetting; return results[0]; } else { throw new Error("Couldn't create group membership or find existing group membership."); } } else { throw e; } } ``` ```java theme={null} package com.box; import java.io.BufferedReader; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import com.box.sdk.BoxAPIException; import com.box.sdk.BoxConfig; import com.box.sdk.BoxDeveloperEditionAPIConnection; import com.box.sdk.BoxGroup; import com.box.sdk.BoxGroupMembership; import com.box.sdk.BoxUser; import com.box.sdk.BoxGroupMembership.Role; import com.eclipsesource.json.JsonObject; public class BoxPlayground { public static void main(String[] args) throws Exception { Path configPath = Paths.get("config.json"); try (BufferedReader reader = Files.newBufferedReader(configPath, Charset.forName("UTF-8"))) { String marketingManagerGroupID = "839982796"; String marketingManagerUserID = "275111793"; BoxConfig boxConfig = BoxConfig.readFrom(reader); BoxDeveloperEditionAPIConnection serviceAccountClient = BoxDeveloperEditionAPIConnection .getAppEnterpriseConnection(boxConfig); BoxGroupMembership.Info marketingManagerMembership = null; BoxGroup marketingManagerGroup = new BoxGroup(serviceAccountClient, marketingManagerGroupID); try { marketingManagerMembership = marketingManagerGroup .addMembership(new BoxUser(serviceAccountClient, marketingManagerUserID), Role.ADMIN); } catch (BoxAPIException e) { JsonObject errorMessage = JsonObject.readFrom(e.getResponse()); int status = errorMessage.get("status").asInt(); if (status == 409) { System.out.println("Found existing membership"); Iterable memberships = marketingManagerGroup.getAllMemberships(); for (BoxGroupMembership.Info membership: memberships) { if (membership.getUser().getID().intern() == marketingManagerUserID) { marketingManagerMembership = membership; break; } } if (marketingManagerMembership == null) { throw new Exception("Couldn't add user to group or find existing membership"); } } else { throw e; } } System.out.println(marketingManagerMembership.getID()); System.out.println(marketingManagerMembership.getRole()); } } } ``` ```csharp theme={null} using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Box.V2; using Box.V2.Config; using Box.V2.Exceptions; using Box.V2.JWTAuth; using Box.V2.Models; using Box.V2.Models.Request; using Newtonsoft.Json.Linq; namespace BoxPlayground { public class Program { static void Main(string[] args) { ExecuteMainAsync().Wait(); } private static async Task ExecuteMainAsync() { using(FileStream fs = new FileStream("./config.json", FileMode.Open)) { var marketingManagerGroupId = "839982796"; var marketingManagerUserId = "275111793"; var session = new BoxJWTAuth(BoxConfig.CreateFromJsonFile(fs)); var serviceAccountClient = session.AdminClient(session.AdminToken()); BoxGroupMembership marketingManagerMembership; try { marketingManagerMembership = await serviceAccountClient.GroupsManager.AddMemberToGroupAsync(new BoxGroupMembershipRequest { User = new BoxRequestEntity { Id = marketingManagerUserId }, Group = new BoxGroupRequest { Id = marketingManagerGroupId }, Role = "admin" }); } catch(BoxException e) { var errorMessage = JObject.Parse(e.Message); if (errorMessage.GetValue("status").ToObject < int > () == 409) { var groups = await serviceAccountClient.GroupsManager.GetAllGroupMembershipsForGroupAsync(marketingManagerGroupId, autoPaginate: true); marketingManagerMembership = groups.Entries.Find((group) = >{ return group.User.Id == marketingManagerUserId; }); if (marketingManagerMembership == null) { throw new Exception("Couldn't create new collaboration or find existing collaboration"); } } else { throw e; } } System.Console.WriteLine(marketingManagerMembership.Id); } } } } ``` ```shell theme={null} box groups:membership:add $user_id $group_id --role=admin ``` # Delete webhooks (v1) Source: https://developer.box.com/guides/webhooks/v1/delete-v1 V1 webhooks cannot be fully deleted. Instead, the webhook can be set back to Developer Mode by [support][support]. Developers can also remove the application from their account by revisiting the enablement URL and clicking **Remove**. [support]: https://support.box.com/hc/en-us/requests/new # Create webhooks (v2) Source: https://developer.box.com/guides/webhooks/v2/create-v2 V2 webhooks can monitor specific files or folders. They can be created in the [Developer Console][console] and with API. ## Developer console V2 webhooks can be created only when the scope **Manage Webhooks** is selected and the application is authorized. See more about Required Access Scopes and authorization. To create a webhook follow the steps below. 1. Navigate to your application in the [Developer Console][console]. 2. Select the **Webhooks** tab. 3. Click the **Create webhook** button. 4. Select **V2** from the drop-down list. 5. Fill in the form. 6. Click **Create webhook** button to save your changes. ### Required fields | Field name | Description | Required | | - | - | - | | URL Address | URL address to be notified by the webhook. | Yes | | Content type | Type of content (file/folder) the webhook is configured for. | Yes | | Triggers | Different triggers that activate the webhook. | Yes | ## API This API requires the application to have the **Manage Webhooks** scope enabled. To attach a webhook to a folder, call the create webhook endpoint with the type of `folder`, the ID of the folder, a URL to send webhook notifications to, and a list of triggers. ```sh cURL theme={null} curl -i -X POST "https://api.box.com/2.0/webhooks" \ -H "authorization: Bearer " \ -H "content-type: application/json" \ -d '{ "target": { "id": "21322", "type": "file" }, "address": "https://example.com/webhooks", "triggers": [ "FILE.PREVIEWED" ] }' ``` ```typescript Node/TypeScript v10 theme={null} await client.webhooks.createWebhook({ target: { id: folder.id, type: 'folder' as CreateWebhookRequestBodyTargetTypeField, } satisfies CreateWebhookRequestBodyTargetField, address: 'https://example.com/new-webhook', triggers: ['FILE.UPLOADED' as CreateWebhookRequestBodyTriggersField], } satisfies CreateWebhookRequestBody); ``` ```python Python v10 theme={null} client.webhooks.create_webhook( CreateWebhookTarget(id=folder.id, type=CreateWebhookTargetTypeField.FOLDER), "https://example.com/new-webhook", [CreateWebhookTriggers.FILE_UPLOADED], ) ``` ```csharp .NET v10 theme={null} await client.Webhooks.CreateWebhookAsync(requestBody: new CreateWebhookRequestBody(target: new CreateWebhookRequestBodyTargetField() { Id = folder.Id, Type = CreateWebhookRequestBodyTargetTypeField.Folder }, address: "https://example.com/new-webhook", triggers: Array.AsReadOnly(new [] {new StringEnum(CreateWebhookRequestBodyTriggersField.FileUploaded)}))); ``` ```swift Swift v10 theme={null} try await client.webhooks.createWebhook(requestBody: CreateWebhookRequestBody(target: CreateWebhookRequestBodyTargetField(id: folder.id, type: CreateWebhookRequestBodyTargetTypeField.folder), address: "https://example.com/new-webhook", triggers: [CreateWebhookRequestBodyTriggersField.fileUploaded])) ``` ```java Java v10 theme={null} client.getWebhooks().createWebhook(new CreateWebhookRequestBody(new CreateWebhookRequestBodyTargetField.Builder().id(folder.getId()).type(CreateWebhookRequestBodyTargetTypeField.FOLDER).build(), "https://example.com/new-webhook", Arrays.asList(CreateWebhookRequestBodyTriggersField.FILE_UPLOADED))) ``` ```java Java v5 theme={null} // Listen for preview events for a file BoxFile file = new BoxFile(api, id); BoxWebHook.Info webhookInfo = BoxWebHook.create(file, url, BoxWebHook.Trigger.FILE.PREVIEWED); ``` ```py Python v4 theme={null} file = client.file(file_id='12345') webhook = client.create_webhook(file, ['FILE.PREVIEWED'], 'https://example.com') print(f'Webhook ID is {webhook.id} and the address is {webhook.address}') ``` ```csharp .NET v6 theme={null} var webhookParams = new BoxWebhookRequest() { Target = new BoxRequestEntity() { Type = BoxType.file, Id = "22222" }, Triggers = new List() { "FILE.PREVIEWED" }, Address = "https://example.com/webhook" }; BoxWebhook webhook = await client.WebhooksManager.CreateWebhookAsync(webhookParams); ``` ```js Node v4 theme={null} // Attach a webhook that sends a notification to https://example.com/webhook when // file 11111 is renamed or downloaded. client.webhooks.create( '11111', client.itemTypes.FILE, 'https://example.com/webhook', [ client.webhooks.triggerTypes.FILE.RENAMED, client.webhooks.triggerTypes.FILE.DOWNLOADED ]) .then(webhook => { /* webhook -> { id: '12345', type: 'webhook', target: { id: '11111', type: 'file' }, created_by: { type: 'user', id: '33333', name: 'Example User', login: 'user@example.com' }, created_at: '2016-05-09T17:41:27-07:00', address: 'https://example.com/webhook', triggers: [ 'FILE.RENAMED', 'FILE.UPLOADED' ] } */ }); ``` Webhooks do cascade, so if a webhook is set on a parent folder, it will also monitor sub-folders for the selected triggers. ## Ownership It is best practice and strongly recommended to create webhooks with a Service Account, or user that will not be deleted, to avoid potential issues with webhook delivery due to loss of access to content. Similar to files and folders, webhooks are owned by a user. If a user who owns a webhook is deleted, they will lose access to all files and folders that they previously had access to. Their webhooks will begin to fail validation, but the webhook service will continue to send events and require retries. ## Webhook address The notification URL specified in the `address` parameter must be a valid URL that you specify when you create a webhook. Every time one of the triggers is activated, this URL is called. The notification URL must use standard port `443` and should return an HTTP status in the range of `200` to `299` within 30 seconds of receiving the webhook payload. ## Webhook triggers The triggers are a list of strings that specify the events which cause the webhook to fire. For example, if you want the webhook to be triggered when a user uploads a file, use `FILE.UPLOADED`. You can find a list of available triggers in this guide. [console]: https://app.box.com/developers/console # Delete webhooks (v2) Source: https://developer.box.com/guides/webhooks/v2/delete-v2 You can delete a webhook using the [Developer Console][console] or API. ## Developer Console To delete a webhook follow the steps below. 1. Navigate to the **Webhooks** tab in the [Developer Console][console]. 2. Select the webhook you want to delete by clicking on its ID. 3. Click the **Delete** button. 4. Confirm the action by clicking **Delete** under the warning message. ## API To remove a webhook from a file or folder, you need to use the remove webhook endpoint with the ID of the webhook. You can get this value using the list all webhooks endpoint. ```sh cURL theme={null} curl -i -X DELETE "https://api.box.com/2.0/webhooks/3321123" \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.webhooks.deleteWebhookById(webhook.id!); ``` ```python Python v10 theme={null} client.webhooks.delete_webhook_by_id(webhook.id) ``` ```csharp .NET v10 theme={null} await client.Webhooks.DeleteWebhookByIdAsync(webhookId: NullableUtils.Unwrap(webhook.Id)); ``` ```swift Swift v10 theme={null} try await client.webhooks.deleteWebhookById(webhookId: webhook.id!) ``` ```java Java v10 theme={null} client.getWebhooks().deleteWebhookById(webhook.getId()) ``` ```py Python v4 theme={null} client.webhook(webhook_id='12345').delete() print('The webhook was successfully deleted!') ``` ```csharp .NET v6 theme={null} await client.WebhooksManager.DeleteWebhookAsync("11111"); ``` ```js Node v4 theme={null} client.webhooks.delete('1234') .then(() => { // deletion succeeded — no value returned }); ``` ## Automatic webhook deletion Using this endpoint is not the only way a webhook can be deleted. The following reasons can cause webhooks to be deleted. * Deleting a Box application automatically deletes all webhooks associated with it. * Deleting all active Access Tokens associated with a webhook automatically deletes the webhook. This includes Developer Tokens and password. * The last successful notification was delivered 30 days ago to the set URL and the period between the last successful notification delivery and the last user trigger event date exceeds 14 days. Let's go through a scenario in which the user downloads a file. This action triggers the webhook to use the set URL to delete a shared link. The diagram illustrates this scenario, showing when the webhook will be deleted. Delete webhooks * **User event trigger**: when the user initiated the event, for example downloaded a file. * **Notification trigger**: when the notification was sent to the webhook, saying that the file was downloaded. * **Last notification delivery**: when the webhook sent a message to a specific URL, for example to delete a shared link. In all of these cases Box sends a webhook payload with the `WEBHOOK.DELETED` event name to the notification URL. The body of the payload includes the following additional information. ```json theme={null} "additional_info": { "reason": "auto_cleanup" } ``` [console]: https://app.box.com/developers/console # V2 webhooks Source: https://developer.box.com/guides/webhooks/v2/index Understand the payload headers, JSON body, and delivery retry behavior of Box V2 webhooks. A V2 webhook sends an HTTP payload to your application when a triggering event occurs on a file or folder. This guide describes the payload Box sends and how Box handles failed deliveries. ## Flow
Webhook flow
When an event triggers a webhook for a file or a folder, Box makes an HTTP call to the `address` specified when the webhook was created. The payload of this call contains some request headers, and a JSON body. ## Payload headers The payload sent by a webhook has the following Box-specific headers. | Header | Description | | - | - | | `BOX-DELIVERY-ID` | A unique ID assigned by Box that identifies the delivered webhook payload. When Box retries a webhook, this ID changes, while the ID in the payload body remains the same. | | `BOX-DELIVERY-TIMESTAMP` | An RFC-3339 timestamp that identifies when the payload was sent. | | `BOX-SIGNATURE-PRIMARY` | A signature created using the primary signature key configured for this webhook. | | `BOX-SIGNATURE-SECONDARY` | A signature created using the secondary signature key configured for this webhook. | | `BOX-SIGNATURE-VERSION` | Value is always `1`. | | `BOX-SIGNATURE-ALGORITHM` | Value is always `HmacSHA256`. | For example: ```shell theme={null} BOX-DELIVERY-ID: 673a081b-bb4b-4d45-b4f1-4131a29c1d07 BOX-DELIVERY-TIMESTAMP: 2016-07-11T10:10:33-07:00 BOX-SIGNATURE-PRIMARY: isCeDp7mLR41/MjcSEFLag9bWmpJkgmN80Je4VIESdo= BOX-SIGNATURE-SECONDARY: 1UbiiKS7/2o5vNIlyMh7e5QGCHq8lflWFgEF+YWBugI= BOX-SIGNATURE-VERSION: 1 BOX-SIGNATURE-ALGORITHM: HmacSHA256 USER-AGENT: Box-WH-Client/0.1 ``` We recommend setting up and verifying signatures of the webhook payloads. HTTP header names are case-insensitive. Your client should convert all header names to a standardized lowercase or uppercase format before trying to determine the value of a header. ## Payload body The body of a webhook payload is a JSON object that describes the file or folder (target) that triggered the webhook, and the event that was triggered. | Field | Description | | - | - | | `type` | Value is always `webhook_event`. | | `id` | A unique ID assigned by Box that identifies an event. When Box retries a webhook, this ID does not change, while the ID in the header changes between calls. | | `created_at` | The date and time when an event was triggered. | | `trigger` | The name of the action that triggered an event, for example `FILE.UPLOADED`. | | `webhook` | The webhook for which an event was triggered. | | `created_by` | The user who triggered an event. | | `source` | The item that triggered an event, for example the file that was uploaded to the target folder. | Example: ```json theme={null} { "type": "webhook_event", "id": "eb0c4e06-751f-442c-86f8-fd5bb404dbec", "created_at": "2016-07-11T10:10:32-07:00", "trigger": "FILE.UPLOADED", "webhook": { "id": "53", "type": "webhook" }, "created_by": { "type": "user", "id": "226067247", "name": "John Q. Developer", "login": "johnq@dev.name" }, "source": { "id": "73835521473", "type": "file", "file_version": { "type": "file_version", "id": "78096737033", "sha1": "2c61623e86bee78e6ab444af456bccc7a1164095" }, "sequence_id": "0", "etag": "0", "sha1": "2c61623e86bee78e6ab444af456bccc7a1164095", "name": "Test-Image-3.png", "description": "", "size": 26458, "path_collection": { "total_count": 4, "entries": [ { "type": "folder", "id": "0", "sequence_id": null, "etag": null, "name": "All Files" }, { "type": "folder", "id": "2614853901", "sequence_id": "4", "etag": "4", "name": "Testing" }, { "type": "folder", "id": "8290186265", "sequence_id": "0", "etag": "0", "name": "Webhooks Base" }, { "type": "folder", "id": "8290188973", "sequence_id": "0", "etag": "0", "name": "Webhooks" } ] }, "created_at": "2016-07-11T10:10:32-07:00", "modified_at": "2016-07-11T10:10:32-07:00", "trashed_at": null, "purged_at": null, "content_created_at": "2016-06-08T11:14:04-07:00", "content_modified_at": "2016-06-08T11:14:04-07:00", "created_by": { "type": "user", "id": "226067247", "name": "John Q. Developer", "login": "johnq@dev.name" }, "modified_by": { "type": "user", "id": "226067247", "name": "John Q. Developer", "login": "johnq@dev.name" }, "owned_by": { "type": "user", "id": "226067247", "name": "John Q. Developer", "login": "johnq@dev.name" }, "shared_link": null, "parent": { "type": "folder", "id": "8290188973", "sequence_id": "0", "etag": "0", "name": "Webhooks" }, "item_status": "active" }, "additional_info": [] } ``` ## Retries Delivery of a webhook payload fails when Box does not receive a response with an HTTP status code in the `200` to `299` range within 30 seconds of sending the payload. Box retries webhook deliveries up to 5 times over a period of 1 hour. These numbers could be subject to change. # Webhook limitations Source: https://developer.box.com/guides/webhooks/v2/limitations-v2 ## One webhook per item There's a limit of one webhook for each item (file or folder), each application and each authenticated user. Once a webhook is attached to an item, no second webhook can be attached, even if the second webhook would respond to a different trigger event. Example: a webhook is set up by `John Doe` to watch `FILE.UPLOADED` events in a folder with the name `Junk`, for an application named `CleanupApp`. At that point, no second webhook can be added to the `Junk` folder by the `CleanupApp` by `John Doe`, even if it is to trigger for a `FILE.DOWNLOADED` event. To listen to another event, update the existing webhook or create a new application. ## 1000 webhooks limit There is a limit of 1000 webhooks for each application and each user. To create more webhooks for a user, create another application or update existing webhooks to apply to higher levels in the folder tree. ## Notification URL restrictions The notification URL or `address` for a webhook must be a valid HTTPS URL that resolves to a valid IP address. It needs to have a certificate signed by a reputable certificate authority. Box does not support self-signed SSL certificates. The IP address of the server must be publicly accessible from the internet and cannot be a `(*.)box.com` address. The port used in the URL must be the standard HTTPS port (`443`). Notifications will not be delivered to other ports. The supported TLS protocol versions are `TLS 1.2` and `TLS 1.3` with FIPS-compliant cipher suites. ## No webhooks on root folder V2 webhooks cannot be created on the root folder, which is the folder with ID `0`. Instead, you will need to use a v1 webhook. When the permissions on an item prevent an action from occurring, no notification is sent for the attempted action. ## `NO_ACTIVE_SESSION` is set in the webhook payload If the auth session (access token) for the app you used to create a webhook expires, that webhook no longer sends events with a full payload. In that case, the event trigger is `NO_ACTIVE_SESSION`. ### JWT Auth For webhooks created with the JWT Auth app, the session expires when you delete the app authorization for this app in the Admin Console. For more information, see [application authorization guide][app authorization]. ### OAuth 2.0 For webhooks created with OAuth 2.0 Auth app, the session expires when both the access token and the refresh token for the user and app used for creating that webhook expire. ### Developer token As the developer token cannot be refreshed and expires after 1 hour, the event trigger `NO_ACTIVE_SESSION` is set in the webhook payload after 1 hour. ## Reasons for webhook deletion The following reasons can cause webhooks to be deleted. 1. Deleting a Box application automatically deletes all webhooks associated with it. 2. Deleting all active Access Tokens associated with a webhook automatically deletes the webhook. This includes Developer Tokens and password. 3. A webhook is automatically deleted if the last successful delivery was 30 days ago and the period between the last successful delivery and the last trigger date is more than 14 days. In all of these cases Box sends a webhook payload with the `WEBHOOK.DELETED` event name to the notification URL. The body of the payload includes the following additional information. ```json theme={null} "additional_info": { "reason": "auto_cleanup" } ``` [app authorization]: https://support.box.com/hc/en-us/articles/360043697014-Authorizing-Apps-in-the-Box-App-Approval-Process # List Webhooks for a User Source: https://developer.box.com/guides/webhooks/v2/list-v2 To fetch all webhooks for the authenticated user, use the list all webhooks endpoint. ```sh cURL theme={null} curl -i -X GET "https://api.box.com/2.0/webhooks" \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.webhooks.getWebhooks(); ``` ```python Python v10 theme={null} client.webhooks.get_webhooks() ``` ```cs .NET v10 theme={null} await client.Webhooks.GetWebhooksAsync(); ``` ```swift Swift v10 theme={null} try await client.webhooks.getWebhooks() ``` ```java Java v10 theme={null} client.getWebhooks().getWebhooks() ``` ```java Java v5 theme={null} Iterable webhooks = BoxWebHook.all(api); for (BoxWebHook.Info webhookInfo: webhooks) { // Do something with the webhook. } ``` ```python Python v4 theme={null} webhooks = client.get_webhooks() for webhook in webhooks: print(f'The webhook ID is {webhook.id} and the address is {webhook.address}') ``` ```cs .NET v6 theme={null} BoxCollectionMarkerBased webhooks = await client.WebhooksManager.GetWebhooksAsync(); ``` ```javascript Node v4 theme={null} client.webhooks.getAll() .then(webhooks => { /* webhooks -> { next_marker: 'ZmlQZS0xLTE%3D', entries: [ { id: '1234', type: 'webhook', target: { id: '22222', type: 'folder' } }, { id: '5678', type: 'webhook', target: { id: '11111', type: 'file' } } ], limit: 2 } */ }); ``` This endpoint requires the application to have the **Manage Webhooks** scope enabled. This API call will only list the webhooks for the authenticated user, not for any other users in the enterprise. This endpoint only returns V2 webhooks. V1 webhooks created in the Developer Console are not included in the response. They are only visible in the **Webhooks** tab of the Developer Console. Also, a Developer Token authenticates as the user logged in to the Developer Console, while a JWT or Client Credentials Grant app authenticates as its Service Account by default. Webhooks created with one of these identities will not appear when you list webhooks with a token for a different identity. # Verify Box webhook signatures Source: https://developer.box.com/guides/webhooks/v2/signatures-v2 Verify Box webhook payloads with HMAC signatures so you can reject requests that were not sent by Box. Webhook signatures help ensure that a webhook payload was sent by Box and was not tampered with. Signatures greatly reduce the likelihood of a successful man-in-the-middle or replay attack. When signatures are configured, Box generates a cryptographic digest of the notification's body and attaches it to the header of the webhook payload. When your application receives the payload, verify the signatures by calculating the same digest and comparing it to the one received. If the digests do not match, the payload should not be trusted. You can achieve an extra level of protection by frequently changing the signature keys. To enable a smooth transition between the old and new keys, Box supports two simultaneous signature keys. ## Signature configuration In order to attach signatures to an application's notifications, you must first generate signature keys for the application. To configure your application's keys follow the steps below. 1. Navigate to the application in the developer console. 2. Click on the **Webhooks** tab. 3. Click the **Manage signature keys** button. 4. Click the **Generate Key** button to configure your keys. Once generating the primary or secondary key, copy the value. You will need it to verify the webhook payloads. Every webhook will now include a `BOX-SIGNATURE-PRIMARY` and `BOX-SIGNATURE-SECONDARY` header payload. ## Signature verification with SDKs Although it is possible to verify signatures manually, methods are provided for your convenience in the official Box SDKs. ## Manual signature verification The following steps describe the basics of how to verify a signature. ### Timestamp validation Check if the timestamp in the `BOX-DELIVERY-TIMESTAMP` header of the payload is not older than ten minutes. ```js theme={null} var timestamp = headers['BOX-DELIVERY-TIMESTAMP']; var date = Date.parse(timestamp); var expired = Date.now() - date > 10*60*1000; ``` ```python theme={null} import dateutil.parser import pytz import datetime timestamp = headers["BOX-DELIVERY-TIMESTAMP"] date = dateutil.parser.parse(timestamp).astimezone(pytz.utc) now = datetime.datetime.now(pytz.utc) delta = datetime.timedelta(minutes=10) expiry_date = now - deltaMinutes expired = date >= expiry_date ``` ### Calculate HMAC signature Calculate the HMAC of the payload using either one of the two configured signatures for the application in the [Developer Console][console]. Ensure you append the bytes of the payload body first, and then the bytes of the timestamp found in the `BOX-DELIVERY-TIMESTAMP` header. ```js theme={null} var crypto = require('crypto'); var primaryKey = '...'; var secondaryKey = '...'; var payload = '{"type":"webhook_event"...}'; var hmac1 = crypto.createHmac('sha256', primaryKey); hmac1.update(payload); hmac1.update(timestamp); var hmac2 = crypto.createHmac('sha256', secondaryKey); hmac2.update(payload); hmac2.update(timestamp); ``` ```python theme={null} import hmac import hashlib primary_key = '...' secondary_key = '...' payload = "{\"type\":\"webhook_event\"...}" bytes = bytes(payload, 'utf-8') + bytes(timestamp, 'utf-8') hmac1 = hmac.new(primary_key, bytes, hashlib.sha256).digest() hmac2 = hmac.new(secondary_key, bytes, hashlib.sha256).digest() ``` ### Base64 Conversion Convert the HMAC to a `Base64` encoded digest. ```js theme={null} var digest1 = hmac1.digest('base64'); var digest2 = hmac2.digest('base64'); ``` ```python theme={null} import base64 digest1 = base64.b64encode(hmac1) digest2 = base64.b64encode(hmac2) ``` ### Signature comparison Compare the encoded digest with the value of the `BOX-SIGNATURE-PRIMARY` or `BOX-SIGNATURE-SECONDARY` headers. Compare the value of the `BOX-SIGNATURE-PRIMARY` header to the digest created with the primary key, and the value of the `BOX-SIGNATURE-SECONDARY` header to the digest created with the secondary key. Make sure to use a timing-safe comparison between signatures to prevent timing attacks. ```js theme={null} const crypto = require('crypto'); function compareSignatures(expectedSignature, receivedSignature) { const expectedBuffer = Buffer.from(expectedSignature, 'base64'); const receivedBuffer = Buffer.from(receivedSignature, 'base64'); if (expectedBuffer.length !== receivedBuffer.length) { return false; } return crypto.timingSafeEqual(expectedBuffer, receivedBuffer); } const signature1 = headers['BOX-SIGNATURE-SECONDARY']; const signature2 = headers['BOX-SIGNATURE-PRIMARY']; const primarySignatureValid = compareSignatures(digest1, signature1) const secondarySignatureValid = compareSignatures(digest2, signature2) const valid = !expired && (primarySignatureValid || secondarySignatureValid) ``` ```python theme={null} import hmac def compare_signatures(expected_signature: Optional[str], received_signature: Optional[str]) -> bool: if not expected_signature or not received_signature: return False if len(expected_signature) != len(received_signature): return False return hmac.compare_digest(expected_signature, received_signature) signature1 = headers["BOX-SIGNATURE-SECONDARY"] signature2 = headers["BOX-SIGNATURE-PRIMARY"] primary_sig_valid = compare_signatures(digest1, signature1) secondary_sig_valid = compare_signatures(digest2, signature2) valid = not expired and (primary_sig_valid or secondary_sig_valid) ``` HTTP header names are case insensitive. Your client should convert all header names to a standardized lowercase or uppercase format before trying to determine the value of a header. ## Rotate signatures When enabled, Box sends two signatures with every webhook payload. Your application can trust a payload as long as at least one of its signatures is valid. When updating one signature key at a time your application will always receive a payload with at least one valid signature. ### Rotation steps These instructions assume that you have already created a primary and secondary key in the [Developer Console][console] and you are ready to replace either of them. By following these steps you can configure your application with two new keys without any conflicts. 1. Go to the **Webhooks** tab in the [Developer Console][console]. 2. Click the **Manage signature keys**. 3. Click the **Reset** button to change the primary key. 4. Update your application with the new primary key. Your application can still receive notifications with the old primary key, but your webhooks should be processed correctly since the secondary key is still valid. 5. Once you are confident that no webhooks with the old primary key are in-flight, you can update the secondary key using the same process. [console]: https://app.box.com/developers/console # Update Webhooks Source: https://developer.box.com/guides/webhooks/v2/update-v2 You can update a webhook using the [Developer Console][console] or API. ## Developer Console To update a webhook in the [Developer Console][console], follow the steps below. 1. Go to the **Webhooks** tab in the [Developer Console][console] to display all webhooks. 2. Select the webhook you want to update by clicking on its ID. 3. Click the **Edit webhook** button. 4. Fill in the data you want to update. 5. Click the **Update** button to save your changes. The list of webhooks contains the following fields: **ID**, **Address**, **Content**, **Created by**, and **Created date**. ## API To update a webhook, use the update webhook endpoint, which requires the webhook ID. To find the ID of the webhook, use the list all webhooks endpoint. ```sh cURL theme={null} curl -i -X PUT "https://api.box.com/2.0/webhooks/3321123" \ -H "authorization: Bearer " \ -H "content-type: application/json" \ -d '{ "triggers": [ "FILE.DOWNLOADED" ] }' ``` ```typescript Node/TypeScript v10 theme={null} await client.webhooks.updateWebhookById(webhook.id!, { requestBody: { address: 'https://example.com/updated-webhook', } satisfies UpdateWebhookByIdRequestBody, } satisfies UpdateWebhookByIdOptionalsInput); ``` ```python Python v10 theme={null} client.webhooks.update_webhook_by_id( webhook.id, address="https://example.com/updated-webhook" ) ``` ```csharp .NET v10 theme={null} await client.Webhooks.UpdateWebhookByIdAsync(webhookId: NullableUtils.Unwrap(webhook.Id), requestBody: new UpdateWebhookByIdRequestBody() { Address = "https://example.com/updated-webhook" }); ``` ```swift Swift v10 theme={null} try await client.webhooks.updateWebhookById(webhookId: webhook.id!, requestBody: UpdateWebhookByIdRequestBody(address: "https://example.com/updated-webhook")) ``` ```java Java v10 theme={null} client.getWebhooks().updateWebhookById(webhook.getId(), new UpdateWebhookByIdRequestBody.Builder().address("https://example.com/updated-webhook").build()) ``` ```java Java v5 theme={null} BoxWebHook webhook = new BoxWebHook(api, id); BoxWebHook.Info info = webhook.new Info(); info.setAddress(url); webhook.update(info); ``` ```py Python v4 theme={null} update_object = { 'triggers': ['FILE.COPIED'], 'address': 'https://newexample.com', } webhook = client.webhook(webhook_id='12345').update_info(data=update_object) print(f'Updated the webhook info for triggers: {webhook.triggers} and address: {webhook.address}') ``` ```csharp .NET v6 theme={null} var updates = new BoxWebhookRequest() { Id = "12345", Address = "https://example.com/webhooks/fileActions }; BoxWebhook updatedWebhook = await client.WebhooksManager.UpdateWebhookAsync(updates); ``` ```js Node v4 theme={null} client.webhooks.update('678901', {address: "https://example.com/webhooks/fileActions"}) .then(webhook => { /* webhook -> { id: '1234', type: 'webhook', target: { id: '22222', type: 'folder' }, created_by: { type: 'user', id: '33333', name: 'Example User', login: 'user@example.com' }, created_at: '2016-05-09T17:41:27-07:00', address: 'https://example.com/webhooks/fileActions', triggers: [ 'FILE.DOWNLOADED', 'FILE.UPLOADED' ] } */ }); ``` [console]: https://app.box.com/developers/console # Box AI API agent configurator Source: https://developer.box.com/agent-configurator Configure Box AI API agents with a visual configurator. Choose from multiple AI models from leading providers, all accessible through a single, easy-to-use platform. Fine-tune Box AI API and LLM parameters, then export a ready-to-use JSON configuration for your Box AI API requests. # Box Agent Skills Source: https://developer.box.com/ai/agent-skills Agent Skills are pre-built instruction sets that teach AI coding assistants how to perform specific tasks. Box Agent Skills give your AI assistant the context it needs to build Box integrations, work with Box content via MCP tools, configure webhooks, and use Box AI retrieval. No manual configuration is required. The skills follow the [Agent Skills](https://agentskills.io/) open standard and can be installed as a plugin for [ChatGPT/Codex](https://chatgpt.com/plugins/plugin_asdk_app_695bfc98071c8191bac7bc479aa27de7), [Cursor](https://cursor.com/dashboard/plugins?plugin-id=1092), or [Claude/Claude Code](https://claude.ai/directory/plugins/box%40knowledge-work-plugins). To get access to the full plugin with all the skills, ensure that you are logged in to the selected AI tool. For detailed reference material, additional examples, and contributing guidelines, see the [box-for-ai repository](https://github.com/box/box-for-ai) on GitHub. A free developer account gives you access to the Box AI API, Developer Console, and everything you need to start building AI-powered workflows. ## Install Running `npx skills add` prompts you to install the `skills` package if not already present. Confirm the installation when prompted to continue. Run this in your project directory (or from any directory for a user-level install) to add all Box Agent Skills: ```bash theme={null} npx skills add box/skills ``` To browse the full list of available skills, see the [Box skills registry](https://skills.sh/box/skills). Box Agent Skills can also be installed as a Codex plugin. See the [Codex setup guide](https://github.com/box/box-for-ai/blob/main/.codex-plugin/README.md) for configuration instructions, including how to connect the Box MCP server. Box Agent Skills can also be installed as a Cursor plugin. See the [Cursor setup guide](https://github.com/box/box-for-ai/blob/main/.cursor-plugin/README.md) for configuration instructions, including how to connect the Box MCP server. For Claude Code, install Box Agent Skills as a platform plugin. See the [Claude Code setup guide](https://github.com/box/box-for-ai/blob/main/.claude-plugin/README.md) for configuration instructions. To verify that your Box account is connected after installation, run `box users:get me --json` in the Box CLI. A successful response confirms your authentication is working. ## Usage Skills are loaded automatically when your assistant detects a relevant task. Ask your assistant to perform Box-related tasks in natural language and it will select the appropriate skill. | What to prompt | What happens | | - | - | | "Add Box file upload to my app" | Scaffolds file upload integration using the Box SDK. | | "Create a shared link for this folder" | Generates code to create and configure shared links. | | "Set up Box webhooks for new file events" | Sets up webhook listeners for file creation events. | | What to prompt | What happens | | - | - | | "Search my Box account for invoices" | Uses the Box Search API to find matching files. | | "Use Box AI to classify documents" | Implements document classification with the Box AI API. | | "Wire webhooks to process new uploads" | Connects webhook events to document processing logic. | | What to prompt | What happens | | - | - | | "Extract invoice numbers and totals from these PDFs, and put them in a table" | Locates the PDFs, runs Box AI extraction on each, and formats results into a structured table. | | "Organize the files in this folder by type. Classify with Box AI and move into subfolders" | Inventories the folder, classifies files using Box AI, creates target subfolders, and moves files serially. | | "Build a search-first retrieval flow for invoice lookup that only downloads files when needed" | Searches Box with filters first, then retrieves content only for the matching shortlist. | | What to prompt | What happens | | - | - | | "Debug 401 errors with my Box JWT auth" | Walks through JWT authentication troubleshooting. | | "Fix webhook signature verification" | Diagnoses and resolves webhook signature issues. | ## Skill structure Box Agent Skills follow the [Agent Skills open standard](https://agentskills.io/). Each skill is a directory containing instruction files and supporting resources. ```text theme={null} box/ SKILL.md # Skill manifest with frontmatter, routing table, workflow steps, and guardrails references/ # Feature-specific deep dives (auth, content workflows, MCP, AI/retrieval, etc.) examples/ # Example prompts ``` Skills are concise by design. They use tables instead of prose and focus on the information an AI assistant needs to generate accurate code. # Box AI API Source: https://developer.box.com/ai/box-ai-api Ask questions, extract metadata, generate text, and summarize files in Box with the Box AI API. The Box AI API summarizes your content, answers questions, generates text, and extracts structured metadata through a single API. The API is available to all Box customers on Business plans and above. A free developer account gives you access to the Box AI API. See the benefits of document summarization, question answering, and metadata extraction for yourself. ## Capabilities | Capability | Description | Endpoint | | - | - | - | | **Ask questions** | Get answers about document content or an entire [Box Hub](/guides/hubs-api/index) | `POST /ai/ask` | | **Generate text** | Create drafts, templates, and content from prompts | `POST /ai/text_gen` | | **Extract metadata** | Pull key-value pairs from unstructured documents using natural language prompts | `POST /ai/extract` | | **Extract structured metadata** | Extract data using metadata templates or field definitions | `POST /ai/extract_structured` | ## Quick starts Get up and running with Box AI in minutes. Summarize document content using Box AI and the Python SDK. Use metadata templates to extract structured data from documents. Extract data using natural language prompts or stringified data structures. Use advanced reasoning models to extract data from complex documents. ## API reference `POST /ai/ask` `POST /ai/text_gen` `POST /ai/extract` `POST /ai/extract_structured` `GET /ai/agent_default` Response objects # AI integrations Source: https://developer.box.com/ai/integrations Connect Box to LangChain, LlamaIndex, Pinecone, Weaviate, and other AI frameworks. Use Box Hubs and Box AI Ask for permission-aware RAG. Connect Box content to the AI tools and frameworks your team already uses. Build retrieval-augmented generation pipelines, power vector search, feed data into analytics, and extend AI agent capabilities. Start with [Box-managed retrieval](/ai/rag) (Hubs and Box AI Ask) when the documents already live in Box and answers must respect Box permissions. Use the loaders and vector databases below when you need a custom index. A free developer account gives you access to the Box AI API and everything you need to build AI-powered workflows with these integrations. ## Agent frameworks Build AI agents that interact with Box content. A Python library for building AI agents that interact with Box. Provides authentication, content interaction, and more. Build AI agents using the Pydantic AI framework with the Box MCP server. ## RAG frameworks Include Box content in retrieval-augmented generation pipelines. These loaders copy file text into your index. After that, Box no longer enforces who can open each file. Unless you filter results, a query can return content the current user cannot access in Box. See [keep an exported index permission-aware](/ai/rag#keep-an-exported-index-permission-aware). Include Box content in your AI workflows. Use the Box loader for LangChain.js in JavaScript-based AI workflows. Ingest Box content into LlamaIndex retrieval pipelines. ## Vector databases Connect Box content to vector databases for semantic search and similarity matching. Connect Box content to Pinecone for vector search workflows. Build RAG workflows with Box content and the Weaviate vector database. ## Enterprise platforms Connect Box to enterprise platforms for agent-based and data workflows. Extend Box for Salesforce with reusable Agentforce actions that automate workflows and enhance agent-based processes. Integrate Box with Snowflake using the Openflow Connector. Extract Box data using the Airbyte source connector for data pipelines. ## Learning resources Tutorials and walkthroughs for building AI-powered workflows with Box. Online course from DeepLearning.AI on building AI applications with MCP servers and Box files. Build multi-agent workflows using the OpenAI SDK and Box. Use the Box MCP server with LangChain MCP Adapters for agent orchestration. Build AI-powered document generation with the Box MCP server and Pydantic AI. # Build RAG over enterprise documents stored in Box Source: https://developer.box.com/ai/rag Build retrieval-augmented generation (RAG) over documents in Box. Start with Box Hubs and Box AI Ask for permission-aware answers. Export to Pinecone or Weaviate, or use LangChain, when you need a custom retrieval stack. Start with **Box-managed retrieval**: add files to a Box Hub and call Box AI Ask. Box retrieves content, grounds the answer, and checks the requesting user's permissions. You do not operate your own embedding pipeline or vector database for that path. Build your own pipeline (chunk, embed, store, generate) only when Box-managed retrieval does not cover your use case. A free developer account gives you access to the Box AI API, Hubs, and the content APIs you need to test both paths. ## Which approach to use | If you need… | Do this | | - | - | | Answers on documents already in Box, with citations and the same permissions as the web app | Hub + `POST /2.0/ai/ask` | | A governed knowledge base (staging, approval, then query) | Access-controlled AI knowledge base | | An index that mixes Box with other sources, a custom embedding model, or custom chunking and ranking | Export Box content to LangChain, LlamaIndex, Pinecone, or Weaviate. Enforce permissions in your retrieval layer | ## Box-managed retrieval Box AI Ask with a hub as the item: * Indexes hub content. No vector store to host. * Returns answers only from files the calling user or app can access. * Includes citations to source files. * Stays current as files and hub items change. No re-index job to run. For a full walkthrough, see Access-controlled AI knowledge base. For working apps, see the RFP answer bank and company brain tutorials. ## Your own RAG pipeline Export from Box when you need: * A retrieval engine or embedding model Box AI does not offer. * A single index that combines Box documents with other sources. * Custom chunking, ranking, or evaluation. * Model hosting you control. Typical flow: 1. Choose folders or files the application can read. Do not index the enterprise root. 2. Pull text with Box representations or your own parsers. See the Pinecone and Weaviate tutorials. 3. Chunk text, generate embeddings, and store vectors with Box file IDs and permission metadata. 4. At query time, embed the question, search the index, then drop chunks the current user cannot access in Box. 5. Send the question plus allowed chunks to an LLM. Cite Box file IDs in the answer. Framework loaders: LangChain, LangChain.js, and LlamaIndex. ## Keep an exported index permission-aware Box does not enforce permissions inside Pinecone, Weaviate, or your LLM prompt. After you copy text out of Box, your service is responsible for access control. At a minimum: * Store the Box file ID and version ID on every vector. * Before returning results, check the querying user's Box permissions. Drop chunks from files the user cannot open. * Refresh the index when files, collaborations, or sharing change. Use events or webhooks, not a one-time crawl. * Re-check access at query time even if you cached permissions at index time. Sharing changes faster than most batch jobs. If your main requirement is permission-aware answers and the content is already in Box, use hubs and Ask instead. See Box-managed retrieval or your own RAG pipeline. # Box and Pinecone Source: https://developer.box.com/ai/vector-databases/pinecone Build a RAG pipeline by connecting Box content to Pinecone for vector search and question answering. This tutorial walks you through building a retrieval-augmented generation (RAG) pipeline using [Pinecone](https://www.pinecone.io/) as a vector database. You will create vector embeddings from files stored in Box, store them in Pinecone, and query them through a large language model (LLM) to answer questions about your content. A free developer account gives you access to the Box API and everything you need to build AI-powered workflows with Pinecone. ## RAG concept overview ### What is a vector database? Pinecone is a managed, cloud-native [vector database](https://www.pinecone.io/learn/vector-database/). A vector database stores and retrieves representations of your data, called embeddings, so you can find results that are similar in meaning rather than literally identical. This is the foundation of semantic search. ### What are embeddings? Embeddings are a mathematical representation of the meaning of your content. They are generated by taking input data, splitting (or chunking) it, and using an embedding model to produce a sequence of floating-point values — for example, `[0.3, 0.4, 0.1, 1.8, 1.1, ...]`. You can think of these values as mapping concepts to points in a high-dimensional space. Similar concepts cluster together: "cat" is close to "kitten," while "banana" and "dog" are far apart. Different embedding models are trained on different data and may be specialized for specific use cases. ### How does RAG work with Box and Pinecone? In a typical RAG workflow: * **Indexing**: Content from a Box folder is extracted, chunked, and converted into embeddings using an embedding model. These embeddings are stored in Pinecone along with metadata such as the file name and a reference back to the source file in Box. * **Retrieval**: When a user asks a question, the question is converted into embeddings using the same model. Pinecone's query functionality retrieves the content chunks most relevant to the question. * **Generation**: The original question and the retrieved content chunks are combined into a prompt and sent to an LLM, which generates an answer grounded in your Box content. The LLM doesn't have direct access to your Box files. RAG bridges that gap by providing the relevant context so the model can generate accurate answers based on your data. Pinecone does not enforce Box permissions. After you embed file text, your application has to filter retrieval for the querying user, store Box file IDs on every vector, and re-index when files, versions, or collaborations change. If you only need permission-aware answers over Box content, use Hubs and Box AI Ask instead. See Build RAG over enterprise documents stored in Box. ## Prerequisites Before you begin, make sure you have the following: * A **Box folder** with files you want to query. The folder must be accessible by the user account that creates the Box application. Note the folder ID from the URL bar. You will need it later. * A **Pinecone account**. [Sign up for a free starter plan](https://docs.pinecone.io/guides/get-started/quickstart) and generate an API key. * For the purpose of this tutorial, an **OpenAI account**. This can be substituted for any other LLM. [Sign up for OpenAI](https://platform.openai.com/docs/quickstart) and generate an API key. You may need to attach billing information. * [Python](https://www.python.org/downloads/) installed on your machine. ## Create a Box custom application Create an OAuth application in the [Box Developer Console](https://cloud.app.box.com/developers/console): 1. Click **New App** in the top right corner. 2. Enter an app name and select **OAuth 2.0** as the authentication method. 3. Click **Create App**. 4. Scroll down to **Redirect URIs** and add: `http://127.0.0.1:5000/callback` 5. Check all boxes under **Application Scopes**. 6. Click **Save Changes**. Note the **Client ID** and **Client Secret**. You will need these for the configuration file. ## Create a Pinecone index 1. Log in to the [Pinecone Console](https://app.pinecone.io/). 2. Click **Create Index**. 3. Give the index a name (for example, `pinecone-demo`). 4. Set the **Dimensions** field to `1024`. 5. Leave all other settings at their defaults and click **Create Index**. Create a Pinecone index Note the index name. You will add it to the configuration file. Pinecone index screen You can also create an index programmatically via the [Pinecone API](https://docs.pinecone.io/reference/api/latest/control-plane/create_index). ## Initialize the code repository Clone the [sample code](https://github.com/box-community/box-pinecone-sample) to your local machine: ```bash theme={null} git clone https://github.com/box-community/box-pinecone-sample.git cd box-pinecone-sample ``` Create and activate a virtual environment: ```bash theme={null} python3 -m venv venv source venv/bin/activate ``` Install the dependencies: ```bash theme={null} pip install -r requirements.txt ``` Copy the sample configuration file and update it with your credentials: ```bash theme={null} cp sample_config.py config.py ``` Open `config.py` in your editor and fill in: * Your Box **Client ID** and **Client Secret** * Your **Box folder ID** * Your **Pinecone API key** and **index name** * Your **OpenAI API key** Configuration file Do **not** set the folder ID to `0`. This would attempt to index your entire Box account, which is not recommended. It would exceed rate limits, consume significant resources, and likely fail to complete. ## Create and store embeddings With the configuration complete, run the main script to create embeddings from your Box content: ```bash theme={null} python main.py ``` The first time you run the application, a browser window opens asking you to grant access. This is the standard OAuth 2.0 authorization flow. Click **Grant Access to Box**. The OAuth tokens are stored in a `.oauth.json` file in the project directory. The refresh token remains valid for 60 days of inactivity. The script processes each file in the specified Box folder, extracts its text representation, chunks it, generates embeddings using the [Pinecone Inference API](https://docs.pinecone.io/reference/api/latest/inference/generate-embeddings), and stores them in Pinecone. Metadata, including a reference back to the source file in Box, is attached to each vector. Once complete, you can view the embeddings in the Pinecone Console along with the associated metadata (file name, plain text of the chunk, and more). This metadata is useful for filtering responses and managing document versions. Embeddings in Pinecone Console ## Query the LLM With the embeddings stored, you can ask questions about your Box content. This part of the project uses OpenAI as the LLM provider. Run the query script: ```bash theme={null} python query.py ``` Enter a question at the prompt. The script converts your question to an embedding, retrieves the most relevant content chunks from Pinecone, and sends them along with your question to OpenAI to generate an answer. Query answer from the service ## Enhancement ideas Store the Box file ID on every vector. At query time, drop chunks from files the current user cannot open in Box. Subscribe to events or webhooks so you re-embed on upload, version change, move, delete, and collaboration updates. Cached permission metadata goes stale; re-check access when you retrieve. The sample runs indexing on demand. You could create a scheduled task or an event-driven service using Box events that triggers when files in the Box folder change. The script uses upserts, so re-running it updates existing records. The query script runs via the command line. You could build a web UI for a more user-friendly question-and-answer experience. The demo uses OAuth 2.0. You could integrate JWT or Client Credentials Grant authentication for server-to-server use cases. The embeddings use the Pinecone Inference API, and the query script uses OpenAI. You can substitute different embedding models, LLM providers, vector dimensions, distance metrics, and chunk sizes to fit your use case. The script processes files that have a text representation in Box (automatically created for supported file types under 500 MB). You could add third-party libraries to handle additional content types or larger files. ## Resources Clone the Box + Pinecone sample repository on GitHub. Learn more about vector databases and how Pinecone works. # Box and Weaviate Source: https://developer.box.com/ai/vector-databases/weaviate Build an end-to-end RAG workflow by embedding Box content into Weaviate and querying it with the Weaviate Query Agent. This tutorial walks you through building a retrieval-augmented generation (RAG) workflow by embedding Box content into a [Weaviate](https://weaviate.io/) vector database and using Weaviate's [Query Agent](https://weaviate.io/blog/weaviate-agents) to answer questions about your data. The complete recipe is available as a Jupyter Notebook in the [Weaviate recipes repository](https://github.com/weaviate/recipes/tree/main/integrations/data-platforms/box). A free developer account gives you access to the Box API and everything you need to build AI-powered workflows with Weaviate. ## Overview ### What is Weaviate? [Weaviate](https://weaviate.io/) is an open-source vector database built for speed, scale, and AI-driven search. It stores data as objects and vectors, letting you combine semantic search via embeddings with structured filtering. Weaviate is cloud native, fault tolerant, and integrates directly with large language models (LLMs). ### How Box and Weaviate create an end-to-end RAG solution RAG is a technique that pairs vector search (retrieval) with a language model (generation) to answer questions using your own data. The flow works as follows: * **Content storage**: Box holds your files (PDFs, docs, text reports, and other supported formats). * **Embedding creation**: Text is extracted from your Box files, chunked, and converted into vector embeddings using [Weaviate Embeddings](https://weaviate.io/developers/wcs/embeddings). * **Querying**: Weaviate's Query Agent takes a natural language question, generates the necessary search and aggregation queries, and returns a single answer — all using agentic RAG. Weaviate does not enforce Box permissions. After you embed file text, your application has to filter retrieval for the querying user, store Box file IDs on every object, and re-index when files, versions, or collaborations change. If you only need permission-aware answers over Box content, use [Hubs and Box AI Ask](/guides/hubs-api/access-controlled-knowledge-base) instead. See [Build RAG over enterprise documents stored in Box](/ai/rag). ## Prerequisites Before you begin, make sure you have the following: * A Box developer account. If you don't already have one, [sign up for a free developer account](https://account.box.com/signup/developer). * A Jupyter Notebook environment such as [Visual Studio Code](https://code.visualstudio.com/docs/datascience/jupyter-notebooks) with the Jupyter extension, or a local Jupyter installation. * A Weaviate Cloud account. [Sign up for a free sandbox tier](https://console.weaviate.cloud/). ## Get a Box developer token 1. Click **New App** in the top right corner. 2. Enter an app name and select **OAuth 2.0** as the authentication method. 3. Click **Create App**. 4. Under **Application Scopes**, add read/write scopes for files if not already enabled, then click **Save Changes**. 5. From the **Configuration** tab, copy and save the developer token. You will need it for the notebook. Developer tokens are valid for 60 minutes. If your session takes longer, you will need to generate a new token. ## Create a Weaviate cluster 1. Log in to [Weaviate Cloud](https://console.weaviate.cloud/). 2. Create a new cluster from the dashboard. You can name it whatever you like. 3. Once the cluster is ready, go to the **Details** tab and note the **cluster URL** and **API key**. ## Run the recipe ### Clone the repository Clone or download the [Weaviate recipes repository](https://github.com/weaviate/recipes): ```bash theme={null} git clone https://github.com/weaviate/recipes.git ``` Navigate to the Box integration folder: ```bash theme={null} cd recipes/integrations/data-platforms/box ``` Open the Jupyter Notebook (`weaviate_box.ipynb`) in your development environment. Weaviate recipes repository structure ### Configure authentication The notebook includes a step to set authentication variables. Update the code block in **step 3** with: * Your **Box developer token** * Your **Weaviate cluster URL** and **API key** Authentication variables in the notebook ### Run the notebook Execute each cell in the notebook sequentially. The notebook: 1. Uploads demo files to Box (or uses files you provide). 2. Extracts text content from the Box files. 3. Chunks the text and creates vector embeddings in Weaviate. 4. Uses the Weaviate Query Agent to answer questions about the content. The repository includes a `demo_files` folder with four 10-K financial reports for testing. You can replace these with your own files if you prefer to work with different content. The final cell demonstrates querying your data. You can modify the query in **step 7** to ask different questions based on your content. Final answer from the Weaviate Query Agent ## Next steps Store the Box file ID on every Weaviate object. At query time, drop results from files the current user cannot open in Box. Re-run embedding when files change, and when collaborations change, using [events](/guides/events) or [webhooks](/guides/webhooks). Upload additional files to Box such as annual reports, articles, or any documents you want to search, and rerun the notebook to embed them in Weaviate. Adjust the `system_prompt` parameter to change the agent's behavior. For example, you can request more detailed analysis or a specific response format. Weaviate offers additional agent types. The [Transformation Agent](https://docs.weaviate.io/agents#transformation-agent) can preprocess your data, and the [Personalization Agent](https://docs.weaviate.io/agents#personalization-agent) can tailor responses to individual users. ## Resources The complete Jupyter Notebook in the Weaviate recipes repository. Learn about Weaviate's agentic RAG capabilities. Documentation for Weaviate's embedding service. Share feedback and get support from other Box developers. # Create agents Source: https://developer.box.com/guides/ai-studio/ai-studio-agents/create-agents Create a custom AI agent using the Box AI Studio API. Box AI Studio is available only for Enterprise Advanced accounts. The `POST /2.0/ai_agents` endpoint creates a new custom AI agent with configurable capabilities for asking questions, generating text, or extracting metadata. ## Before you start Make sure you have completed the steps in Getting started with AI Studio to create a platform app and generate a developer token. ## Send a request ```typescript Node/TypeScript v10 theme={null} await client.aiStudio.createAiAgent({ name: agentName, accessState: 'enabled', ask: new AiStudioAgentAsk({ accessState: 'enabled', description: 'desc1' }), } satisfies CreateAiAgentInput); ``` ```python Python v10 theme={null} client.ai_studio.create_ai_agent( agent_name, "enabled", ask=AiStudioAgentAsk(access_state="enabled", description="desc1"), ) ``` ```cs .NET v10 theme={null} await client.AiStudio.CreateAiAgentAsync(requestBody: new CreateAiAgent(name: agentName, accessState: "enabled") { Ask = new AiStudioAgentAsk(accessState: "enabled", description: "desc1") }); ``` ```swift Swift v10 theme={null} try await client.aiStudio.createAiAgent(requestBody: CreateAiAgent(name: agentName, accessState: "enabled", ask: AiStudioAgentAsk(accessState: "enabled", description: "desc1"))) ``` ```java Java v10 theme={null} client.getAiStudio().createAiAgent(new CreateAiAgent.Builder(agentName, "enabled").ask(new AiStudioAgentAsk("enabled", "desc1")).build()) ``` ### Parameters To make a call, you must pass the following parameters. Mandatory parameters are in **bold**. | Parameter | Description | Example | | - | - | - | | **`type`** | The type of agent used to handle queries. | `ai_agent` | | **`name`** | The name of the AI Agent. | `My AI Agent` | | **`access_state`** | The state of the AI Agent. Value is one of `enabled` `disabled`. | `enabled` | | `icon_reference` | The icon reference of the AI Agent. It should have format of the URL `https://cdn01.boxcdn.net/app-assets/aistudio/avatars/`, where the possible values of `file_name` are: `logo_boxAi.png`,`logo_stamp.png`, `logo_legal.png`,`logo_finance.png`,`logo_config.png`,`logo_handshake.png` `logo_analytics.png`,`logo_classification.png`. | `https://cdn01.boxcdn.net/app-assets/aistudio/avatars/logo_analytics.svg` | | `allowed_entities` | List of allowed users or groups. | | | `ask` | The AI Agent to be used for ask. | `ask` | | `extract` | The AI Agent to be used for extraction. | | | `text_gen` | The AI agent used for generating text. | | # Delete AI agents Source: https://developer.box.com/guides/ai-studio/ai-studio-agents/delete-agents Permanently delete a custom AI agent using the Box AI Studio API. Box AI Studio is available only for Enterprise Advanced accounts. The `DELETE /2.0/ai_agents/{id}` endpoint permanently removes a custom AI agent. This action cannot be undone. ## Before you start Make sure you have completed the steps in Getting started with AI Studio to create a platform app and generate a developer token. ## Send a request ```sh cURL theme={null} curl -i -L -X DELETE "https://api.box.com/2.0/ai_agents/12345" \ -H 'Authorization: Bearer ' ``` ```typescript Node/TypeScript v10 theme={null} await client.aiStudio.deleteAiAgentById(createdAgent.id); ``` ```python Python v10 theme={null} client.ai_studio.delete_ai_agent_by_id(created_agent.id) ``` ```cs .NET v10 theme={null} await client.AiStudio.DeleteAiAgentByIdAsync(agentId: createdAgent.Id); ``` ```swift Swift v10 theme={null} try await client.aiStudio.deleteAiAgentById(agentId: createdAgent.id) ``` ```java Java v10 theme={null} client.getAiStudio().deleteAiAgentById(createdAgent.getId()) ``` ### Parameters To make a call, you must pass the following parameters. Mandatory parameters are in **bold**. | Parameter | Description | Example | | - | - | - | | **`agent_id`** | The ID of the agent to delete. | `1234` | # Get AI agent by ID Source: https://developer.box.com/guides/ai-studio/ai-studio-agents/get-agent-id Retrieve a specific AI agent's configuration using the Box AI Studio API. Box AI Studio is available only for Enterprise Advanced accounts. The `GET /2.0/ai_agents/{id}` endpoint returns the configuration for a specific AI agent. ## Before you start Make sure you have completed the steps in Getting started with AI Studio to create a platform app and generate a developer token. ## Send a request ```sh cURL theme={null} curl -i -X GET "https://api.box.com/2.0/ai_agents/1234567890" \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.aiStudio.getAiAgentById(createdAgent.id, { queryParams: { fields: ['ask'] } satisfies GetAiAgentByIdQueryParams, } satisfies GetAiAgentByIdOptionalsInput); ``` ```python Python v10 theme={null} client.ai_studio.get_ai_agent_by_id(created_agent.id, fields=["ask"]) ``` ```cs .NET v10 theme={null} await client.AiStudio.GetAiAgentByIdAsync(agentId: createdAgent.Id, queryParams: new GetAiAgentByIdQueryParams() { Fields = Array.AsReadOnly(new [] {"ask"}) }); ``` ```swift Swift v10 theme={null} try await client.aiStudio.getAiAgentById(agentId: createdAgent.id, queryParams: GetAiAgentByIdQueryParams(fields: ["ask"])) ``` ```java Java v10 theme={null} client.getAiStudio().getAiAgentById(createdAgent.getId(), new GetAiAgentByIdQueryParams.Builder().fields(Arrays.asList("ask")).build()) ``` ### Parameters To make a call, you must pass the following parameters. Mandatory parameters are in **bold**. | Parameter | Description | Example | | - | - | - | | **`agent_id`** | The agent id to get. | `1234` | | `fields` | The fields to return in the response. | `ask` | # List agents Source: https://developer.box.com/guides/ai-studio/ai-studio-agents/get-agents List all AI agents in your enterprise using the Box AI Studio API. Box AI Studio is available only for Enterprise Advanced accounts. The `GET /2.0/ai_agents` endpoint lists all AI agents in your enterprise, with optional filters for mode, state, and fields. ## Before you start Make sure you have completed the steps in Getting started with AI Studio to create a platform app and generate a developer token. ## Send a request ```sh cURL theme={null} curl -i -X GET "https://api.box.com/2.0/ai_agents" \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.aiStudio.getAiAgents(); ``` ```python Python v10 theme={null} client.ai_studio.get_ai_agents() ``` ```csharp .NET v10 theme={null} await client.AiStudio.GetAiAgentsAsync(); ``` ```swift Swift v10 theme={null} try await client.aiStudio.getAiAgents() ``` ```java Java v10 theme={null} client.getAiStudio().getAiAgents() ``` ### Parameters To make a call, you can pass the following parameters. | Parameter | Description | Example | | - | - | - | | `mode` | The mode to filter the agent configuration to return. Possible values are: `ask`, `text_gen`, and `extract`. | `ask` | | `fields` | The fields to return in the response. Value is one of `ask`, `text_gen`, `extract`. | `ask` | | `agent_state` | The state of the agent to return. Value is one of `enabled`, `disabled`. | `enabled` | | `include_box_default` | Whether to include the Box default agents in the response. | `true` | | `limit` | The maximum number of items to return per page. | `1000` | | `marker` | Defines the position marker at which to begin returning results. | `JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVii` | # AI Studio agents Source: https://developer.box.com/guides/ai-studio/ai-studio-agents/index Create, list, update, and delete custom AI agents through the Box AI Studio API. Box AI Studio is available only for Enterprise Advanced accounts. The AI Studio API provides full CRUD operations for managing custom AI agents. Each agent can be configured with specific capabilities (`ask`, `text_gen`, `extract`), access controls, and custom instructions. `POST /2.0/ai_agents`: define a new agent with capabilities, access state, and allowed entities. `GET /2.0/ai_agents`: retrieve agents filtered by mode, state, or fields. `GET /2.0/ai_agents/{id}`: fetch a specific agent's configuration. `PUT /2.0/ai_agents/{id}`: modify an agent's name, state, or capabilities. `DELETE /2.0/ai_agents/{id}`: permanently remove an agent. # Update AI agents Source: https://developer.box.com/guides/ai-studio/ai-studio-agents/update-agents Update a custom AI agent's configuration using the Box AI Studio API. Box AI Studio is available only for Enterprise Advanced accounts. The `PUT /2.0/ai_agents/{id}` endpoint updates a custom AI agent's name, state, capabilities, or access controls. ## Before you start Make sure you have completed the steps in Getting started with AI Studio to create a platform app and generate a developer token. ## Send a request ```sh cURL theme={null} curl -i -X PUT "https://api.box.com/2.0/ai_agents/1234567890" \ -H "authorization: Bearer " ``` ```typescript Node/TypeScript v10 theme={null} await client.aiStudio.updateAiAgentById(createdAgent.id, { name: agentName, accessState: 'enabled', ask: new AiStudioAgentAsk({ accessState: 'disabled', description: 'desc2' }), } satisfies CreateAiAgentInput); ``` ```python Python v10 theme={null} client.ai_studio.update_ai_agent_by_id( created_agent.id, agent_name, "enabled", ask=AiStudioAgentAsk(access_state="disabled", description="desc2"), ) ``` ```cs .NET v10 theme={null} await client.AiStudio.UpdateAiAgentByIdAsync(agentId: createdAgent.Id, requestBody: new CreateAiAgent(name: agentName, accessState: "enabled") { Ask = new AiStudioAgentAsk(accessState: "disabled", description: "desc2") }); ``` ```swift Swift v10 theme={null} try await client.aiStudio.updateAiAgentById(agentId: createdAgent.id, requestBody: CreateAiAgent(name: agentName, accessState: "enabled", ask: AiStudioAgentAsk(accessState: "disabled", description: "desc2"))) ``` ```java Java v10 theme={null} client.getAiStudio().updateAiAgentById(createdAgent.getId(), new CreateAiAgent.Builder(agentName, "enabled").ask(new AiStudioAgentAsk("disabled", "desc2")).build()) ``` ### Parameters To make a call, you must pass the following parameters. Mandatory parameters are in **bold**. | Parameter | Description | Example | | - | - | - | | **`type`** | The type of agent used to handle queries. | `ai_agent` | | **`name`** | The name of the AI Agent. | My AI Agent | | **`access_state`** | The state of the AI Agent. Value is one of `enabled` `disabled`. | `enabled` | | `icon_reference` | The icon reference of the AI Agent. It should have format of the URL `https://cdn01.boxcdn.net/app-assets/aistudio/avatars/`, where possible values of `file_name` are: `logo_boxAi.png`,`logo_stamp.png`,`logo_legal.png`,`logo_finance.png`,`logo_config.png`,`logo_handshake.png`,`logo_analytics.png`,`logo_classification.png` | `https://cdn01.boxcdn.net/app-assets/aistudio/avatars/logo_analytics.svg` | | `allowed_entities` | List of allowed users or groups. | | | `ask` | The AI Agent to be used for ask. | `ask` | | `extract` | The AI Agent to be used for extraction. | | | `text_gen` | The AI agent used for generating text. | | # Get started with AI Studio Source: https://developer.box.com/guides/ai-studio/getting-started-ai-studio Set up a platform app and authenticate to start creating custom AI agents with the Box AI Studio API. Box AI Studio is available only for Enterprise Advanced accounts. To create custom AI agents with AI Studio, you need a platform application with the Box AI scope enabled and a token to authenticate your API calls. The setup steps below are similar to the Box AI API prerequisites. If you have already completed those steps, confirm that the **Manage AI** scope is enabled and that your admin has enabled AI Studio, then skip ahead to [next steps](#next-steps). Create a platform application to make API calls. Follow the guide on creating platform apps. Ask a Box admin to enable AI Studio in the Admin Console. For admin instructions, see [Enabling Box AI Studio and Managing Agents][enable]. To interact with the Box AI API, add the `ai.readwrite` scope to your application. 1. Open your application in the Developer Console. 2. Go to **Configuration** > **Required Access Scopes** > **Content Actions**. 3. Select **Manage AI**. box ai scopes If you do not see the **Manage AI** option, contact your admin to grant access to the Box AI API. If you see the scope checked and grayed out, the app owner has it enabled but you cannot change the setting. Submit your app for authorization or enablement. If you are enabling Box AI for an existing application, you must re-authorize it. Generate a developer token to authenticate your requests. 1. Go to **Developer Console** > **My Platform Apps**. 2. Hover over a platform app and select the **Options menu** button (...) on the right. 3. Select **Generate Developer Token**. The token is automatically copied to your clipboard. A developer token is only valid for one hour. In production, use your app's configured authentication method (for example, OAuth 2.0 or Client Credentials Grant). For more details, see developer tokens. ## Next steps With your application configured and a token generated, you can start creating custom AI agents. Use cURL, Postman, or any of the Box SDKs to make API calls. Define a custom AI agent with specific capabilities and access controls. Retrieve all AI agents in your enterprise. [enable]: https://support.box.com/hc/en-us/articles/37228079461267-Enabling-Box-AI-Studio-and-Managing-Agents/#h_01JH9HAMP43YYN6VWM51QCK413 # Box AI Studio Source: https://developer.box.com/guides/ai-studio/index Create, manage, and deploy custom AI agents through the Box AI Studio API. Box AI Studio is available only for Enterprise Advanced accounts. Box AI Studio lets you create custom AI agents through the API. Unlike the standard Box AI API endpoints that use Box's default agent configurations, AI Studio agents let you define purpose-built agents with custom instructions, model selections, and access controls. For example, you can create an agent that acts as a compliance consultant -- answering questions about customer documentation with FedRAMP Moderate compliance in mind -- and restricts access to specific users or groups. ## Enabling Box AI Studio Enable Box AI Studio in the Admin Console to start creating agents. For admin instructions, see Enabling Box AI Studio and Managing Agents. Step-by-step instructions are available in the Get started with AI Studio guide. ## How Box AI Studio relates to Box AI | Feature | Box AI API | Box AI Studio | | - | - | - | | **Use case** | Provides endpoints for Q\&A, text generation, and metadata extraction against files in Box | Lets you create and manage custom AI agents with tailored behavior | | **Customization** | Override model and prompt per-request using the `ai_agent` parameter | Define persistent agent configurations with custom instructions, models, and access controls | | **Access** | Business plans and above | Enterprise Advanced only | | **API endpoints** | `POST /ai/ask`, `POST /ai/text_gen`, `POST /ai/extract`, `POST /ai/extract_structured` | `POST /ai_agents`, `GET /ai_agents`, `GET /ai_agents/{id}`, `PUT /ai_agents/{id}`, `DELETE /ai_agents/{id}` | ## Capabilities You can configure AI Studio agents for the following modes: | Mode | Parameter | Description | | - | - | - | | **Ask** | `ask` | Answer user questions about documents | | **Text generation** | `text_gen` | Generate text based on file content and prompts | | **Extraction** | `extract` | Extract metadata from documents | ai agent capabilities ## Get started Create a platform app, enable the AI scope, and generate credentials to start making AI Studio API calls. Use `POST /ai_agents` to create a custom AI agent with specific capabilities and access controls. Retrieve all AI agents in your enterprise, filtered by mode or state. Get, update, and delete agents by ID. For admin setup instructions, see [Enabling Box AI Studio and Managing Agents][ai-studio]. [ai-studio]: https://support.box.com/hc/en-us/articles/37228079461267-Enabling-Box-AI-Studio-and-Managing-Agents # AI model configuration override reference Source: https://developer.box.com/guides/box-ai/ai-agents/ai-agent-overrides Full ai_agent parameter reference with sample configurations and parameter details. The `ai_agent` configuration allows you to override the default AI model configuration. It is available for the following endpoints: * `POST ai/ask` * `POST ai/text_gen` * `POST ai/extract` * `POST ai/extract_structured` Use the `GET ai_agent_default` endpoint to fetch the default configuration before applying overrides. The override examples include: * Replacing the default AI model with a custom one based on your organization's needs. * Tweaking the base `prompt` to allow a more customized user experience. * Changing a parameter, such as `temperature`, to make the results more or less creative. ## Sample configuration A complete configuration for `ai/ask` is as follows: ```sh theme={null} { "type": "ai_agent_ask", "basic_text": { "llm_endpoint_params": { "type": "openai_params", "frequency_penalty": 1.5, "presence_penalty": 1.5, "stop": "<|im_end|>", "temperature": 0, "top_p": 1 }, "model": "openai__gpt_5_mini", "num_tokens_for_completion": 8400, "prompt_template": "It is `{current_date}`, consider these travel options `{content}` and answer the `{user_question}`.", "system_message": "You are a helpful travel assistant specialized in budget travel" }, "basic_text_multi": { "llm_endpoint_params": { "type": "openai_params", "frequency_penalty": 1.5, "presence_penalty": 1.5, "stop": "<|im_end|>", "temperature": 0, "top_p": 1 }, "model": "openai__gpt_5_mini", "num_tokens_for_completion": 8400, "prompt_template": "It is `{current_date}`, consider these travel options `{content}` and answer the `{user_question}`.", "system_message": "You are a helpful travel assistant specialized in budget travel" }, "long_text": { "embeddings": { "model": "openai__text_embedding_ada_002", "strategy": { "id": "basic", "num_tokens_per_chunk": 64 } }, "llm_endpoint_params": { "type": "openai_params", "frequency_penalty": 1.5, "presence_penalty": 1.5, "stop": "<|im_end|>", "temperature": 0, "top_p": 1 }, "model": "openai__gpt_5_mini", "num_tokens_for_completion": 8400, "prompt_template": "It is `{current_date}`, consider these travel options `{content}` and answer the `{user_question}`.", "system_message": "You are a helpful travel assistant specialized in budget travel" }, "long_text_multi": { "embeddings": { "model": "openai__text_embedding_ada_002", "strategy": { "id": "basic", "num_tokens_per_chunk": 64 } }, "llm_endpoint_params": { "type": "openai_params", "frequency_penalty": 1.5, "presence_penalty": 1.5, "stop": "<|im_end|>", "temperature": 0, "top_p": 1 }, "model": "openai__gpt_5_mini", "num_tokens_for_completion": 8400, "prompt_template": "It is `{current_date}`, consider these travel options `{content}` and answer the `{user_question}`.", "system_message": "You are a helpful travel assistant specialized in budget travel" } } ``` ### Differences in parameter sets The set of parameters available for `ask`, `text_gen`, `extract`, `extract_structured` differs slightly, depending on the API call. * The agent configuration for the `ask` endpoint includes `basic_text`, `basic_text_multi`, `long_text` and `long_text_multi` parameters. This is because of the `mode` parameter you use to specify if the request is for a single item or multiple items. If you selected `multiple_item_qa` as the `mode`, you can also use `multi` parameters for overrides. * The agent configuration for `text_gen` includes the `basic_gen` parameter that is used to generate text. ### LLM endpoint params The `llm_endpoint_params` configuration options differ depending on the overall AI model being Google, OpenAI or AWS based. For example, both `llm_endpoint_params` objects accept a `temperature` parameter, but the outcome differs depending on the model. For Google and AWS models, the [`temperature`][google-temp] is used for sampling during response generation, which occurs when `top-P` and `top-K` are applied. Temperature controls the degree of randomness in the token selection. For OpenAI models, [`temperature`][openai-temp] is the sampling temperature with values between 0 and 2. Higher values like 0.8 make the output more random, while lower values like 0.2 make it more focused and deterministic. When introducing your own configuration, use `temperature` or `top_p` but not both. ### System message The `system_message` parameter's aim is to help the LLM understand its role and what it’s supposed to do. For example, if your solution is processing travel itineraries, you can add a system message saying: ```sh theme={null} You are a travel agent aid. You are going to help support staff process large amounts of schedules, tickets, etc. ``` This message is separate from the content you send in, but it can improve the results. ### Number of tokens for completion The `num_tokens_for_completion` parameter represents the number of [tokens][openai-tokens] Box AI can return. This number can vary based on the model used. [openai-tokens]: https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them [google-temp]: https://ai.google.dev/gemini-api/docs/models/generative-models#model-parameters [openai-temp]: https://community.openai.com/t/temperature-top-p-and-top-k-for-chatbot-responses/295542 # AI agent configuration versioning Source: https://developer.box.com/guides/box-ai/ai-agents/ai-agent-versioning See how Box versions AI agent snapshots, the 12-month support guarantee, and transition timelines. Box updates the default models across the endpoints on a regular basis in order to stay up to date with the most advanced options. If a default model is updated, it will be posted in the developer changelog. AI agent configuration versioning gives the developers more control over AI agent versioning and ensures consistent responses. AI agent configuration versioning adopts the following principles: * Each AI agent snapshot is supported for at least 12 months, unless there are factors outside of Box's control. For example, a Large Language Model (LLM) may get deprecated. * An AI agent snapshot is available unless a new, stable agent version is released. * A 6-month window is provided to test and transition to the new snapshot. ## Historical AI agent configuration The values in the default agent configuration used by the LLM gateway often change to achieve the best possible answer quality. To make sure your configurations are not affected in a negative way, you can use the historical AI agent configuration provided below to override the default one. ``````json theme={null} { "ask": { "type": "ai_agent_ask", "longText": { "model": "openai__gpt_5_mini", "systemMessage": "", "promptTemplate": "Reply as if it's {current_date}.\nI will ask you for help and provide subsections of one document delimited by five backticks (`````) at the beginning and at the end.\nIf I make a reference to \"this\", I am referring to the document I provided between the five backticks. I may ask you a question where the answer is contained within the document. In that case, do your best to answer using only the document, but if you cannot, feel free to mention that you couldn't find an answer in the document, but you have some answer from your general knowledge.\nI may ask you to perform some kind of computation or symbol manipulation such as filtering a list, counting something, summing, averaging, and other aggregation/grouping functions or some combination of them. In these cases, first list the plan of how you plan to perform such a computation, then follow that plan step by step, keeping track of intermediate results, and at the end tell me the final answer.\nI may ask you to enumerate or somehow list people, places, characters, or other important things from the document, if I do so, please only use the document provided to list them.\nTEXT FROM DOCUMENT STARTS\n`````\n{content}\n`````\nTEXT FROM DOCUMENT ENDS\nNever mention five backticks in your response. Unless you are told otherwise, a one paragraph response is sufficient for any requested summarization tasks.\nHere is how I need help from you: {user_question}", "numTokensForCompletion": 6000, "llmEndpointParams": { "type": "openai_params", "temperature": 0.0, "topP": 1.0, "frequencyPenalty": 0.0, "presencePenalty": 1.5, "stop": "<|im_end|>" }, "embeddings": { "model": "azure__openai__text_embedding_ada_002", "strategy": { "id": "basic", "numTokensPerChunk": 64 } } }, "basicText": { "model": "openai__gpt_5_mini", "systemMessage": "", "promptTemplate": "Reply as if it's {current_date}.\nI will ask you for help and provide the entire text of one document delimited by five backticks (`````) at the beginning and at the end.\nIf I make a reference to \"this\", I am referring to the document I provided between the five backticks. I may ask you a question where the answer is contained within the document. In that case, do your best to answer using only the document, but if you cannot, feel free to mention that you couldn't find an answer in the document, but you have some answer from your general knowledge.\nI may ask you to perform some kind of computation or symbol manipulation such as filtering a list, counting something, summing, averaging, and other aggregation/grouping functions or some combination of them. In these cases, first list the plan of how you plan to perform such a computation, then follow that plan step by step, keeping track of intermediate results, and at the end tell me the final answer.\nI may ask you to enumerate or somehow list people, places, characters, or other important things from the document, if I do so, please only use the document provided to list them.\nTEXT FROM DOCUMENT STARTS\n`````\n{content}\n`````\nTEXT FROM DOCUMENT ENDS\nNever mention five backticks in your response. Unless you are told otherwise, a one paragraph response is sufficient for any requested summarization tasks.\nHere is how I need help from you: {user_question}", "numTokensForCompletion": 6000, "llmEndpointParams": { "type": "openai_params", "temperature": 0.0, "topP": 1.0, "frequencyPenalty": 0.0, "presencePenalty": 1.5, "stop": "<|im_end|>" } }, "longTextMulti": { "model": "openai__gpt_5_mini", "systemMessage": "Role and Goal: You are an assistant designed to analyze and answer a question based on provided snippets from multiple documents, which can include business-oriented documents like docs, presentations, PDFs, etc. The assistant will respond concisely, using only the information from the provided documents.\n\nConstraints: The assistant should avoid engaging in chatty or extensive conversational interactions and focus on providing direct answers. It should also avoid making assumptions or inferences not supported by the provided document snippets.\n\nGuidelines: When answering, the assistant should consider the file's name and path to assess relevance to the question. In cases of conflicting information from multiple documents, it should list the different answers with citations. For summarization or comparison tasks, it should concisely answer with the key points. It should also consider the current date to be the date given.\n\nPersonalization: The assistant's tone should be formal and to-the-point, suitable for handling business-related documents and queries.\n", "promptTemplate": "Current date: {current_date}\n\nTEXT FROM DOCUMENTS STARTS\n{content}\nTEXT FROM DOCUMENTS ENDS\n\nHere is how I need help from you: {user_question}\n.", "numTokensForCompletion": 6000, "llmEndpointParams": { "type": "openai_params", "temperature": 0.0, "topP": 1.0, "frequencyPenalty": 0.0, "presencePenalty": 1.5, "stop": "<|im_end|>" }, "embeddings": { "model": "azure__openai__text_embedding_ada_002", "strategy": { "id": "basic", "numTokensPerChunk": 64 } } }, "basicTextMulti": { "model": "openai__gpt_5_mini", "systemMessage": "", "promptTemplate": "Current date: {current_date}\n\nTEXT FROM DOCUMENTS STARTS\n{content}\nTEXT FROM DOCUMENTS ENDS\n\nHere is how I need help from you: {user_question}\n.", "numTokensForCompletion": 6000, "llmEndpointParams": { "type": "openai_params", "temperature": 0.0, "topP": 1.0, "frequencyPenalty": 0.0, "presencePenalty": 1.5, "stop": "<|im_end|>" } }, }, "extract": { "type": "ai_agent_extract", "longText": { "model": "google__gemini_1_5_flash_001", "systemMessage": "Respond only in valid json. You are extracting metadata that is name, value pairs from a document. Only output the metadata in valid json form, as {\"name1\":\"value1\",\"name2\":\"value2\"} and nothing else. You will be given the document data and the schema for the metadata, that defines the name, description and type of each of the fields you will be extracting. Schema is of the form {\"fields\": [{\"key\": \"key_name\", \"displayName\": \"key display name\", \"type\": \"string\", \"description\": \"key description\"}]}. Leverage key description and key display name to identify where the key and value pairs are in the document. In certain cases, key description can also indicate the instructions to perform on the document to obtain the value. Prompt will be in the form of Schema is ``schema`` \n document is ````document````", "promptTemplate": "If you need to know today's date to respond, it is {current_date}. Schema is ``{user_question}`` \n document is ````{content}````", "numTokensForCompletion": 4096, "llmEndpointParams": { "type": "google_params", "temperature": 0.0, "topP": 1.0, "frequencyPenalty": 0.0, "presencePenalty": 0.0 }, "embeddings": { "model": "azure__openai__text_embedding_ada_002", "strategy": { "id": "basic", "numTokensPerChunk": 64 } } }, "basicText": { "model": "google__gemini_1_5_flash_001", "systemMessage": "Respond only in valid json. You are extracting metadata that is name, value pairs from a document. Only output the metadata in valid json form, as {\"name1\":\"value1\",\"name2\":\"value2\"} and nothing else. You will be given the document data and the schema for the metadata, that defines the name, description and type of each of the fields you will be extracting. Schema is of the form {\"fields\": [{\"key\": \"key_name\", \"displayName\": \"key display name\", \"type\": \"string\", \"description\": \"key description\"}]}. Leverage key description and key display name to identify where the key and value pairs are in the document. In certain cases, key description can also indicate the instructions to perform on the document to obtain the value. Prompt will be in the form of Schema is ``schema`` \n document is ````document````", "promptTemplate": "If you need to know today's date to respond, it is {current_date}. Schema is ``{user_question}`` \n document is ````{content}````", "numTokensForCompletion": 4096, "llmEndpointParams": { "type": "google_params", "temperature": 0.0, "topP": 1.0, "frequencyPenalty": 0.0, "presencePenalty": 0.0 } } }, "textGen": { "type": "ai_agent_text_gen", "basicGen": { "model": "openai__gpt_5_mini", "systemMessage": "\nIf you need to know today's date to respond, it is {current_date}.\nThe user is working in a collaborative document creation editor called Box Notes.\nAssume that you are helping a business user create documents or to help the user revise existing text.\nYou can help the user in creating templates to be reused or update existing documents, you can respond with text that the user can use to place in the document that the user is editing.\nIf the user simply asks to \"improve\" the text, then simplify the language and remove jargon, unless the user specifies otherwise.\nDo not open with a preamble to the response, just respond.\n", "promptTemplate": "{user_question}", "numTokensForCompletion": 12000, "llmEndpointParams": { "type": "openai_params", "temperature": 0.1, "topP": 1.0, "frequencyPenalty": 0.75, "presencePenalty": 0.75, "stop": "<|im_end|>" }, "embeddings": { "model": "azure__openai__text_embedding_ada_002", "strategy": { "id": "basic", "numTokensPerChunk": 64 } }, "contentTemplate": "`````{content}`````" } }, "extractStructured": { "type": "ai_agent_extract_structured", "longText": { "model": "google__gemini_1_5_flash_001", "systemMessage": "Respond only in valid json. You are extracting metadata that is name, value pairs from a document. Only output the metadata in valid json form, as {\"name1\":\"value1\",\"name2\":\"value2\"} and nothing else. You will be given the document data and the schema for the metadata, that defines the name, description and type of each of the fields you will be extracting. Schema is of the form {\"fields\": [{\"key\": \"key_name\", \"prompt\": \"prompt to extract the value\", \"type\": \"date\"}]}. Leverage prompt for each key to identify where the key and value pairs are in the document. In certain cases, prompt can also indicate the instructions to perform on the document to obtain the value. Prompt will be in the form of Schema is ``schema`` \n document is ````document````", "promptTemplate": "If you need to know today's date to respond, it is {current_date}. Schema is ``{user_question}`` \n document is ````{content}````", "numTokensForCompletion": 4096, "llmEndpointParams": { "type": "google_params", "temperature": 0.0, "topP": 1.0, "frequencyPenalty": 0.0, "presencePenalty": 0.0 }, "embeddings": { "model": "google__textembedding_gecko_003", "strategy": { "id": "basic", "numTokensPerChunk": 64 } } }, "basicText": { "model": "google__gemini_1_5_flash_001", "systemMessage": "Respond only in valid json. You are extracting metadata that is name, value pairs from a document. Only output the metadata in valid json form, as {\"name1\":\"value1\",\"name2\":\"value2\"} and nothing else. You will be given the document data and the schema for the metadata, that defines the name, description and type of each of the fields you will be extracting. Schema is of the form {\"fields\": [{\"key\": \"key_name\", \"prompt\": \"prompt to extract the value\", \"type\": \"date\"}]}. Leverage prompt for each key to identify where the key and value pairs are in the document. In certain cases, prompt can also indicate the instructions to perform on the document to obtain the value. Prompt will be in the form of Schema is ``schema`` \n document is ````document````", "promptTemplate": "If you need to know today's date to respond, it is {current_date}. Schema is ``{user_question}`` \n document is ````{content}````", "numTokensForCompletion": 4096, "llmEndpointParams": { "type": "google_params", "temperature": 0.0, "topP": 1.0, "frequencyPenalty": 0.0, "presencePenalty": 0.0 } } } } `````` # Get default AI agent configuration Source: https://developer.box.com/guides/box-ai/ai-agents/get-agent-default-config Fetch the current default agent configuration for ask, text_gen, extract, and extract_structured modes. The `GET /2.0/ai_agent_default` endpoint allows you to fetch the default configuration for AI services. Once you get the configuration details you can override them using the `ai_agent` parameter. ## Send a request To send a request, use the `GET /2.0/ai_agent_default` endpoint. Make sure you have generated the developer token to authorize your app. See getting started with Box AI for details. ```sh cURL theme={null} curl -i -L -X GET "https://api.box.com/2.0/ai_agent_default?mode=text_gen" \ -H 'Authorization: Bearer ' ``` ```typescript Node/TypeScript v10 theme={null} await client.ai.getAiAgentDefaultConfig({ mode: 'ask' as GetAiAgentDefaultConfigQueryParamsModeField, language: 'en-US', } satisfies GetAiAgentDefaultConfigQueryParams); ``` ```python Python v10 theme={null} client.ai.get_ai_agent_default_config(GetAiAgentDefaultConfigMode.ASK, language="en-US") ``` ```cs .NET v10 theme={null} await client.Ai.GetAiAgentDefaultConfigAsync(queryParams: new GetAiAgentDefaultConfigQueryParams(mode: GetAiAgentDefaultConfigQueryParamsModeField.Ask) { Language = "en-US" }); ``` ```swift Swift v10 theme={null} try await client.ai.getAiAgentDefaultConfig(queryParams: GetAiAgentDefaultConfigQueryParams(mode: GetAiAgentDefaultConfigQueryParamsModeField.ask, language: "en-US")) ``` ```java Java v10 theme={null} client.getAi().getAiAgentDefaultConfig(new GetAiAgentDefaultConfigQueryParams.Builder(GetAiAgentDefaultConfigQueryParamsModeField.ASK).language("en-US").build()) ``` ```java Java v5 theme={null} BoxAIAgentConfig config = BoxAI.getAiAgentDefaultConfig( api, BoxAIAgent.Mode.ASK, "en", "openai__gpt_3_5_turbo" ); ``` ```python Python v4 theme={null} config = client.get_ai_agent_default_config( mode='text_gen', language='en', model='openai__gpt_3_5_turbo' ) print(config) ``` ```javascript Node v4 theme={null} client.ai.getAiAgentDefaultConfig({ mode: 'ask', language: 'en', model:'openai__gpt_3_5_turbo' }).then(response => { /* response -> { "type": "ai_agent_ask", "basic_text": { "llm_endpoint_params": { "type": "openai_params", "frequency_penalty": 1.5, "presence_penalty": 1.5, "stop": "<|im_end|>", "temperature": 0, "top_p": 1 }, "model": "openai__gpt_3_5_turbo", "num_tokens_for_completion": 8400, "prompt_template": "It is `{current_date}`, and I have $8000 and want to spend a week in the Azores. What should I see?", "system_message": "You are a helpful travel assistant specialized in budget travel" }, ... } */ }); ``` ### Parameters To make a call, you must pass the following parameters. Mandatory parameters are in **bold**. | Parameter | Description | Example | | - | - | - | | `language` | The language code the agent configuration is returned for. If the language is not supported, the default configuration is returned. | `ja-JP` | | **`mode`** | The mode used to filter the agent configuration. The value can be `ask`, `text_gen`, `extract`, or `extract_structured` depending on the result you want to achieve. | `ask` | | `model` | The model you want to get the configuration for. To make sure your chosen model is supported, see the list of models. | `openai__gpt_5_mini` | ## Responses The responses to the call may vary depending on the `mode` parameter value you choose. When you set the `mode` parameter to `ask` the response will be as follows: ```sh theme={null} { "type": "ai_agent_ask", "basic_text": { "model": "openai__gpt_5_mini", "system_message": "", "prompt_template": "prompt_template": "{user_question}Write it in an informal way.{content}" }, "num_tokens_for_completion": 6000, "llm_endpoint_params": { "temperature": 0, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 1.5, "stop": "<|im_end|>", "type": "openai_params" } }, "long_text": { "model": "openai__gpt_5_mini", "system_message": "", "prompt_template": "prompt_template": "{user_question}Write it in an informal way.{content}" }, "num_tokens_for_completion": 6000, "llm_endpoint_params": { "temperature": 0, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 1.5, "stop": "<|im_end|>", "type": "openai_params" }, "embeddings": { "model": "azure__openai__text_embedding_ada_002", "strategy": { "id": "basic", "num_tokens_per_chunk": 64 } } }, "basic_text_multi": { "model": "openai__gpt_5_mini", "system_message": "", "prompt_template": "Current date: {current_date}\n\nTEXT FROM DOCUMENTS STARTS\n{content}\nTEXT FROM DOCUMENTS ENDS\n\nHere is how I need help from you: {user_question}\n.", "num_tokens_for_completion": 6000, "llm_endpoint_params": { "temperature": 0, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 1.5, "stop": "<|im_end|>", "type": "openai_params" } }, "long_text_multi": { "model": "openai__gpt_5_mini", "system_message": "Role and Goal: You are an assistant designed to analyze and answer a question based on provided snippets from multiple documents, which can include business-oriented documents like docs, presentations, PDFs, etc. The assistant will respond concisely, using only the information from the provided documents.\n\nConstraints: The assistant should avoid engaging in chatty or extensive conversational interactions and focus on providing direct answers. It should also avoid making assumptions or inferences not supported by the provided document snippets.\n\nGuidelines: When answering, the assistant should consider the file's name and path to assess relevance to the question. In cases of conflicting information from multiple documents, it should list the different answers with citations. For summarization or comparison tasks, it should concisely answer with the key points. It should also consider the current date to be the date given.\n\nPersonalization: The assistant's tone should be formal and to-the-point, suitable for handling business-related documents and queries.\n", "prompt_template": "Current date: {current_date}\n\nTEXT FROM DOCUMENTS STARTS\n{content}\nTEXT FROM DOCUMENTS ENDS\n\nHere is how I need help from you: {user_question}\n.", "num_tokens_for_completion": 6000, "llm_endpoint_params": { "temperature": 0, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 1.5, "stop": "<|im_end|>", "type": "openai_params" }, "embeddings": { "model": "azure__openai__text_embedding_ada_002", "strategy": { "id": "basic", "num_tokens_per_chunk": 64 } } } } ``` When you set the `mode` parameter to `text_gen` the response will be as follows: ``````sh theme={null} { "type": "ai_agent_text_gen", "basic_gen": { "model": "openai__gpt_5_mini", "system_message": "\nIf you need to know today's date to respond, it is {current_date}.\nThe user is working in a collaborative document creation editor called Box Notes.\nAssume that you are helping a business user create documents or to help the user revise existing text.\nYou can help the user in creating templates to be reused or update existing documents, you can respond with text that the user can use to place in the document that the user is editing.\nIf the user simply asks to \"improve\" the text, then simplify the language and remove jargon, unless the user specifies otherwise.\nDo not open with a preamble to the response, just respond.\n", "prompt_template": "{user_question}", "num_tokens_for_completion": 12000, "llm_endpoint_params": { "temperature": 0.1, "top_p": 1, "frequency_penalty": 0.75, "presence_penalty": 0.75, "stop": "<|im_end|>", "type": "openai_params" }, "embeddings": { "model": "azure__openai__text_embedding_ada_002", "strategy": { "id": "basic", "num_tokens_per_chunk": 64 } }, "content_template": "`````{content}`````" } } `````` When you set the `mode` parameter to `extract` the response will be as follows: `````sh theme={null} { "type": "ai_agent_extract", "basic_text": { "model": "google__gemini_1_5_flash_001", "system_message": "Respond only in valid json. You are extracting metadata that is name, value pairs from a document. Only output the metadata in valid json form, as {\"name1\":\"value1\",\"name2\":\"value2\"} and nothing else. You will be given the document data and the schema for the metadata, that defines the name, description and type of each of the fields you will be extracting. Schema is of the form {\"fields\": [{\"key\": \"key_name\", \"displayName\": \"key display name\", \"type\": \"string\", \"description\": \"key description\"}]}. Leverage key description and key display name to identify where the key and value pairs are in the document. In certain cases, key description can also indicate the instructions to perform on the document to obtain the value. Prompt will be in the form of Schema is ``schema`` \n document is ````document````", "prompt_template": "If you need to know today's date to respond, it is {current_date}. Schema is ``{user_question}`` \n document is ````{content}````", "num_tokens_for_completion": 4096, "llm_endpoint_params": { "temperature": 0, "top_p": 1, "top_k": null, "type": "google_params" } }, "long_text": { "model": "google__gemini_1_5_flash_001", "system_message": "Respond only in valid json. You are extracting metadata that is name, value pairs from a document. Only output the metadata in valid json form, as {\"name1\":\"value1\",\"name2\":\"value2\"} and nothing else. You will be given the document data and the schema for the metadata, that defines the name, description and type of each of the fields you will be extracting. Schema is of the form {\"fields\": [{\"key\": \"key_name\", \"displayName\": \"key display name\", \"type\": \"string\", \"description\": \"key description\"}]}. Leverage key description and key display name to identify where the key and value pairs are in the document. In certain cases, key description can also indicate the instructions to perform on the document to obtain the value. Prompt will be in the form of Schema is ``schema`` \n document is ````document````", "prompt_template": "If you need to know today's date to respond, it is {current_date}. Schema is ``{user_question}`` \n document is ````{content}````", "num_tokens_for_completion": 4096, "llm_endpoint_params": { "temperature": 0, "top_p": 1, "top_k": null, "type": "google_params" }, "embeddings": { "model": "azure__openai__text_embedding_ada_002", "strategy": { "id": "basic", "num_tokens_per_chunk": 64 } } } } ````` When you set the `mode` parameter to `extract_structured` the response will be as follows: `````sh theme={null} { "type": "ai_agent_extract_structured", "basic_text": { "model": "google__gemini_1_5_flash_001", "system_message": "Respond only in valid json. You are extracting metadata that is name, value pairs from a document. Only output the metadata in valid json form, as {\"name1\":\"value1\",\"name2\":\"value2\"} and nothing else. You will be given the document data and the schema for the metadata, that defines the name, description and type of each of the fields you will be extracting. Schema is of the form {\"fields\": [{\"key\": \"key_name\", \"prompt\": \"prompt to extract the value\", \"type\": \"date\"}]}. Leverage prompt for each key to identify where the key and value pairs are in the document. In certain cases, prompt can also indicate the instructions to perform on the document to obtain the value. Prompt will be in the form of Schema is ``schema`` \n document is ````document````", "prompt_template": "If you need to know today's date to respond, it is {current_date}. Schema is ``{user_question}`` \n document is ````{content}````", "num_tokens_for_completion": 4096, "llm_endpoint_params": { "temperature": 0, "top_p": 1, "top_k": null, "type": "google_params" } }, "long_text": { "model": "google__gemini_1_5_flash_001", "system_message": "Respond only in valid json. You are extracting metadata that is name, value pairs from a document. Only output the metadata in valid json form, as {\"name1\":\"value1\",\"name2\":\"value2\"} and nothing else. You will be given the document data and the schema for the metadata, that defines the name, description and type of each of the fields you will be extracting. Schema is of the form {\"fields\": [{\"key\": \"key_name\", \"prompt\": \"prompt to extract the value\", \"type\": \"date\"}]}. Leverage prompt for each key to identify where the key and value pairs are in the document. In certain cases, prompt can also indicate the instructions to perform on the document to obtain the value. Prompt will be in the form of Schema is ``schema`` \n document is ````document````", "prompt_template": "If you need to know today's date to respond, it is {current_date}. Schema is ``{user_question}`` \n document is ````{content}````", "num_tokens_for_completion": 4096, "llm_endpoint_params": { "temperature": 0, "top_p": 1, "top_k": null, "type": "google_params" }, "embeddings": { "model": "google__textembedding_gecko_003", "strategy": { "id": "basic", "num_tokens_per_chunk": 64 } } } } ````` [override-tutorials]: /guides/box-ai/ai-agents/ai-agent-overrides # AI model overrides Source: https://developer.box.com/guides/box-ai/ai-agents/index Override the default AI model, prompt, and LLM parameters used by Box AI API endpoints. These guides explain how to inspect and customize the AI agent configurations that Box AI uses behind the scenes. For an overview of the override system, including when to use overrides, the configuration structure by endpoint, and LLM parameter differences by provider, see the Box AI overview. Full `ai_agent` parameter reference with sample payloads for `ask`, `text_gen`, `extract`, and `extract_structured`. Fetch the current defaults with `GET /2.0/ai_agent_default` so you know what you are overriding. See how Box versions agent snapshots, the 12-month support guarantee, and transition timelines. Full list of core and customer-enabled models with capability tiers, compliance badges, and API names. # AWS Claude Haiku 4.5 Source: https://developer.box.com/guides/box-ai/ai-models/aws-claude-4-5-haiku-model-card According to Anthropic, the **AWS Claude Haiku 4.5** model is optimized for high-volume, low-latency applications. It provides strong coding performance with improved speed and cost efficiency compared to larger models. ## Model details | Item | Value | Description | | - | - | - | | Model name | **AWS Claude Haiku 4.5** | The name of the model. | | Model category | **Standard** | The category of the model: Standard or Premium. | | API model name | `aws__claude_4_5_haiku` | The name of the model that is used in the Box AI API for model overrides. The user must provide this exact name for the API to work. | | Compliance | **ISMAP, FedRAMP Moderate** | Government compliance frameworks and authorizations applicable to this model. | | Hosting layer | **Amazon Web Services (AWS)** | The trusted organization that securely hosts the LLM. | | Model provider | **Anthropic** | The organization that provides this model. | | Release date | **October 15th, 2025** | The release date for the model. | | Knowledge cutoff date | **February 2025** | The date after which the model does not get any information updates. | | Input context window | **200k tokens** | The number of tokens supported by the input context window. | | Maximum output tokens | **64k tokens** | The number of tokens that can be generated by the model in a single request. | | Empirical throughput | **Not specified** | The number of tokens the model can generate per second. | | Open source | **No** | Specifies if the model's code is available for public use. | ## Additional documentation For additional information, see [official AWS Claude Haiku 4.5 documentation][aws-claude]. [aws-claude]: https://aws.amazon.com/bedrock/anthropic/ # AWS Claude Opus 4.5 Source: https://developer.box.com/guides/box-ai/ai-models/aws-claude-4-5-opus-model-card According to Anthropic, the **AWS Claude Opus 4.5** model is designed for developers building sophisticated AI agents that can reason, plan, and perform complex tasks with minimal oversight. ## Model details | Item | Value | Description | | - | - | - | | Model name | **AWS Claude Opus 4.5** | The name of the model. | | Model category | **Premium** | The category of the model: Standard or Premium. | | API model name | `aws__claude_4_5_opus` | The name of the model that is used in the Box AI API for model overrides. The user must provide this exact name for the API to work. | | Compliance | **ISMAP, FedRAMP Moderate** | Government compliance frameworks and authorizations applicable to this model. | | Hosting layer | **Amazon Web Services (AWS)** | The trusted organization that securely hosts the LLM. | | Model provider | **Anthropic** | The organization that provides this model. | | Release date | **November 24th, 2025** | The release date for the model. | | Knowledge cutoff date | **March 2025** | The date after which the model does not get any information updates. | | Input context window | **200k tokens** | The number of tokens supported by the input context window. | | Maximum output tokens | **64k tokens** | The number of tokens that can be generated by the model in a single request. | | Empirical throughput | **Not specified** | The number of tokens the model can generate per second. | | Open source | **No** | Specifies if the model's code is available for public use. | ## Additional documentation For additional information, see [official AWS Claude Opus 4.5 documentation][aws-claude]. [aws-claude]: https://aws.amazon.com/bedrock/anthropic/ # AWS Claude Sonnet 4.5 Source: https://developer.box.com/guides/box-ai/ai-models/aws-claude-4-5-sonnet-model-card According to Anthropic, the **AWS Claude Sonnet 4.5** model is a high-performance model designed for building complex agents, delivering leading coding capabilities, and executing across development tasks. It excels at autonomously planning and executing complex, multi-step workflows and is particularly effective in areas like finance, research, and cybersecurity. ## Model details | Item | Value | Description | | - | - | - | | Model name | **AWS Claude Sonnet 4.5** | The name of the model. | | Model category | **Premium** | The category of the model: Standard or Premium. | | API model name | `aws__claude_4_5_sonnet` | The name of the model that is used in the Box AI API for model overrides. The user must provide this exact name for the API to work. | | Compliance | **ISMAP, FedRAMP Moderate** | Government compliance frameworks and authorizations applicable to this model. | | Hosting layer | **Amazon Web Services (AWS)** | The trusted organization that securely hosts the LLM. | | Model provider | **Anthropic** | The organization that provides this model. | | Release date | **September 29th, 2025** | The release date for the model. | | Knowledge cutoff date | **January 2025** | The date after which the model does not get any information updates. | | Input context window | **200k tokens** | The number of tokens supported by the input context window. | | Maximum output tokens | **64k tokens** | The number of tokens that can be generated by the model in a single request. | | Empirical throughput | **Not specified** | The number of tokens the model can generate per second. | | Open source | **No** | Specifies if the model's code is available for public use. | ## Additional documentation For additional information, see [official AWS Claude Sonnet 4.5 documentation][aws-claude]. [aws-claude]: https://aws.amazon.com/bedrock/anthropic/ # AWS Claude Opus 4.6 Source: https://developer.box.com/guides/box-ai/ai-models/aws-claude-4-6-opus-model-card According to Anthropic, **AWS Claude Opus 4.6** is the next generation of Anthropic's most intelligent model designed for coding, enterprise agents, and professional work. ## Model details | Item | Value | Description | | - | - | - | | Model name | **AWS Claude Opus 4.6** | The name of the model. | | Model category | **Premium** | The category of the model: Standard or Premium. | | API model name | `aws__claude_4_6_opus` | The name of the model that is used in the Box AI API for model overrides. The user must provide this exact name for the API to work. | | Compliance | **ISMAP, FedRAMP Moderate** | Government compliance frameworks and authorizations applicable to this model. | | Hosting layer | **Amazon Web Services (AWS)** | The trusted organization that securely hosts the LLM. | | Model provider | **Anthropic** | The organization that provides this model. | | Release date | **February 5th, 2026** | The release date for the model. | | Knowledge cutoff date | **May 2025** | The date after which the model does not get any information updates. | | Input context window | **200k tokens** | The number of tokens supported by the input context window. | | Maximum output tokens | **128k tokens** | The number of tokens that can be generated by the model in a single request. | | Empirical throughput | **Not specified** | The number of tokens the model can generate per second. | | Open source | **No** | Specifies if the model's code is available for public use. | ## Additional documentation For additional information, see [official AWS Claude Opus 4.6 documentation][aws-claude]. [aws-claude]: https://platform.claude.com/docs/en/about-claude/models/overview # AWS Claude Sonnet 4.6 Source: https://developer.box.com/guides/box-ai/ai-models/aws-claude-4-6-sonnet-model-card ## Overview According to Anthropic, **AWS Claude Sonnet 4.6** is a powerful, versatile model built for daily use, scaled production, and complex tasks across coding, agents, and professional workflows. ## Model details | Item | Value | Description | | - | - | - | | Model name | **AWS Claude Sonnet 4.6** | The name of the model. | | Model category | **Premium** | The category of the model: Standard or Premium. | | API model name | `aws__claude_4_6_sonnet` | The name of the model that is used in the Box AI API for model overrides. You must provide this exact name for the API to work. | | Compliance | **ISMAP, FedRAMP Moderate** | Government compliance frameworks and authorizations applicable to this model. | | Hosting layer | **Amazon Web Services (AWS)** | The trusted organization that securely hosts the LLM. | | Model provider | **Anthropic** | The organization that provides this model. | | Release date | **February 17th, 2026** | The release date for the model. | | Knowledge cutoff date | **August 2025** | The date after which the model does not get any information updates. | | Input context window | **1m tokens** | The number of tokens supported by the input context window. | | Maximum output tokens | **64k tokens** | The number of tokens that can be generated by the model in a single request. | | Empirical throughput | **Not specified** | The number of tokens the model can generate per second. | | Open source | **No** | Specifies if the model's code is available for public use. | ## Additional documentation For additional information, see official AWS Claude documentation. # AWS Claude Opus 4.7 Source: https://developer.box.com/guides/box-ai/ai-models/aws-claude-4-7-opus-model-card According to Anthropic, **AWS Claude Opus 4.7** is a highly autonomous model which performs well on long-horizon agentic work, knowledge work, vision tasks, and memory tasks. ## Model details | Item | Value | Description | | - | - | - | | Model name | **AWS Claude Opus 4.7** | The name of the model. | | Model category | **Premium** | The category of the model: Standard or Premium. | | API model name | `aws__claude_4_7_opus` | The name of the model that is used in the Box AI API for model overrides. The user must provide this exact name for the API to work. | | Compliance | **ISMAP, FedRAMP Moderate** | Government compliance frameworks and authorizations applicable to this model. | | Hosting layer | **Amazon Web Services (AWS)** | The trusted organization that securely hosts the LLM. | | Model provider | **Anthropic** | The organization that provides this model. | | Release date | **April 16th, 2026** | The release date for the model. | | Knowledge cutoff date | **January 2026** | The date after which the model does not get any information updates. | | Input context window | **1m tokens** | The number of tokens supported by the input context window. | | Maximum output tokens | **128k tokens** | The number of tokens that can be generated by the model in a single request. | | Empirical throughput | **Not specified** | The number of tokens the model can generate per second. | | Open source | **No** | Specifies if the model's code is available for public use. | ## Additional documentation For additional information, see [official AWS Claude Opus 4.7 documentation][aws-claude]. [aws-claude]: https://aws.amazon.com/bedrock/anthropic/ # AWS Claude Opus 4.8 Source: https://developer.box.com/guides/box-ai/ai-models/aws-claude-4-8-opus-model-card According to Anthropic, **AWS Claude Opus 4.8** is Anthropic's most capable model for complex reasoning, long-horizon agentic coding, and high-autonomy work. ## Model details | Item | Value | Description | | - | - | - | | Model name | **AWS Claude Opus 4.8** | The name of the model. | | Model category | **Premium** | The category of the model: Standard or Premium. | | API model name | `aws__claude_4_8_opus` | The name of the model that is used in the Box AI API for model overrides. The user must provide this exact name for the API to work. | | Compliance | **ISMAP, FedRAMP Moderate** | Government compliance frameworks and authorizations applicable to this model. | | Hosting layer | **Amazon Web Services (AWS)** | The trusted organization that securely hosts the LLM. | | Model provider | **Anthropic** | The organization that provides this model. | | Release date | **May 28th, 2026** | The release date for the model. | | Knowledge cutoff date | **January 2026** | The date after which the model does not get any information updates. | | Input context window | **1m tokens** | The number of tokens supported by the input context window. | | Maximum output tokens | **128k tokens** | The number of tokens that can be generated by the model in a single request. | | Empirical throughput | **Not specified** | The number of tokens the model can generate per second. | | Open source | **No** | Specifies if the model's code is available for public use. | ## Additional documentation For additional information, see [official AWS Claude Opus 4.8 documentation][claude-opus-4-8]. [claude-opus-4-8]: https://platform.claude.com/docs/en/about-claude/models/overview # AWS Claude Sonnet 5 Source: https://developer.box.com/guides/box-ai/ai-models/aws-claude-sonnet-5-model-card ## Overview According to Anthropic, **AWS Claude Sonnet 5** offers the best combination of speed and intelligence. It is the most agentic Sonnet model yet, able to make plans, use tools such as browsers and terminals, and run autonomously. ## Model details | Item | Value | Description | | - | - | - | | Model name | **AWS Claude Sonnet 5** | The name of the model. | | Model category | **Premium** | The category of the model: Standard or Premium. | | API model name | `aws__claude_sonnet_5` | The name of the model that is used in the Box AI API for model overrides. You must provide this exact name for the API to work. | | Compliance | **ISMAP, FedRAMP Moderate** | Government compliance frameworks and authorizations applicable to this model. | | Hosting layer | **Amazon Web Services (AWS)** | The trusted organization that securely hosts the LLM. | | Model provider | **Anthropic** | The organization that provides this model. | | Release date | **June 30th, 2026** | The release date for the model. | | Knowledge cutoff date | **January 2026** | The date after which the model does not get any information updates. | | Input context window | **1m tokens** | The number of tokens supported by the input context window. | | Maximum output tokens | **128k tokens** | The number of tokens that can be generated by the model in a single request. | | Empirical throughput | **Not specified** | The number of tokens the model can generate per second. | | Open source | **No** | Specifies if the model's code is available for public use. | ## Additional documentation For additional information, see official AWS Claude documentation. # IBM Llama 4 Maverick Source: https://developer.box.com/guides/box-ai/ai-models/ibm-llama-4-maverick-model-card According to Meta, **IBM Llama 4 Maverick** is a natively multimodal AI model using a mixture-of-experts architecture, designed for high-performance text and image understanding with support for 12 languages. ## Model details | Item | Value | Description | | - | - | - | | Model name | **IBM Llama 4 Maverick** | The name of the model. | | Model category | **Standard** | The category of the model: Standard or Premium. | | API model name | `ibm__llama_4_maverick` | The name of the model that is used in the [Box AI API for model overrides]. The user must provide this exact name for the API to work. | | Compliance | **N/A** | Government compliance frameworks and authorizations applicable to this model. | | Hosting layer | **IBM** | The trusted organization that securely hosts the LLM. | | Model provider | **Meta** | The organization that provides this model. | | Release date | **April 5th, 2025** | The release date for the model. | | Knowledge cutoff date | **August 2024** | The date after which the model does not get any information updates. | | Input context window | **1m** | The number of tokens supported by the input context window. | | Maximum output tokens | **Not specified** | The number of tokens that can be generated by the model in a single request. | | Empirical throughput | **Not specified** | The number of tokens the model can generate per second. | | Open source | **Yes** | Specifies if the model's code is available for public use. | | IP infringement protection | **No** | Use of this model does not come with any intellectual property rights assurances or protections from Box. Please consider any potential IP issues that might arise from using the model’s outputs. | ## Additional documentation For additional information, see [official IBM Llama 4 Maverick documentation][IBM]. [Box AI API for model overrides]: /guides/box-ai/ai-agents/ai-agent-overrides [IBM]: https://www.ibm.com/docs/en/watsonx/w-and-w/2.2.0?topic=models-third-party-foundation#llama-4 # IBM Mistral Medium 3 Source: https://developer.box.com/guides/box-ai/ai-models/ibm-mistral-medium-3-model-card According to Mistral AI, the **IBM Mistral Medium 3** model is a high-performance enterprise-grade model that delivers frontier-level capabilities, excelling in coding, STEM reasoning, and multimodal understanding. ## Model details | Item | Value | Description | | - | - | - | | Model name | **IBM Mistral Medium 3** | The name of the model. | | Model category | **Standard** | The category of the model: Standard or Premium. | | API model name | `ibm__mistral_medium_2505` | The name of the model that is used in the Box AI API for model overrides. The user must provide this exact name for the API to work. | | Compliance | **N/A** | Government compliance frameworks and authorizations applicable to this model. | | Hosting layer | **IBM** | The trusted organization that securely hosts the LLM. | | Model provider | **Mistral AI** | The organization that provides this model. | | Release date | **May 2025** | The release date for the model. | | Knowledge cutoff date | **Not specified** | The date after which the model does not get any information updates. | | Input context window | **128k tokens** | The number of tokens supported by the input context window. | | Maximum output tokens | **Not specified** | The number of tokens that can be generated by the model in a single request. | | Empirical throughput | **Not specified** | The number of tokens the model can generate per second. | | Open source | **No** | Specifies if the model's code is available for public use. | | IP infringement protection | **No** | Use of this model does not come with any intellectual property rights assurances or protections from Box. Please consider any potential IP issues that might arise from using the model’s outputs. | ## Additional documentation For additional information, see [official Mistral AI documentation][mistral-ai]. [mistral-ai]: https://docs.mistral.ai/getting-started/models/models_overview/ # IBM Mistral Small 3.1 Source: https://developer.box.com/guides/box-ai/ai-models/ibm-mistral-small-3-1-model-card According to Mistral AI, the **IBM Mistral Small 3.1** model is a fast, efficient open-source model with multimodal capabilities and extended context window. It delivers strong performance across text and vision tasks while maintaining low latency, making it suitable for a wide range of applications from conversational AI to document processing. ## Model details | Item | Value | Description | | - | - | - | | Model name | **IBM Mistral Small 3.1** | The name of the model. | | Model category | **Standard** | The category of the model: Standard or Premium. | | API model name | `ibm__mistral_small_3_1_24b_instruct_2503` | The name of the model that is used in the Box AI API for model overrides. The user must provide this exact name for the API to work. | | Compliance | **N/A** | Government compliance frameworks and authorizations applicable to this model. | | Hosting layer | **IBM** | The trusted organization that securely hosts the LLM. | | Model provider | **Mistral AI** | The organization that provides this model. | | Release date | **March 2025** | The release date for the model. | | Knowledge cutoff date | **Not specified** | The date after which the model does not get any information updates. | | Input context window | **128k tokens** | The number of tokens supported by the input context window. | | Maximum output tokens | **Not specified** | The number of tokens that can be generated by the model in a single request. | | Empirical throughput | **150** | The number of tokens the model can generate per second. | | Open source | **Yes** | Specifies if the model's code is available for public use. | | IP infringement protection | **No** | Use of this model does not come with any intellectual property rights assurances or protections from Box. Please consider any potential IP issues that might arise from using the model’s outputs. | ## Additional documentation For additional information, see [official Mistral AI documentation][mistral-ai]. [mistral-ai]: https://docs.mistral.ai/getting-started/models/models_overview/ # Meta Muse Spark 1.2 Source: https://developer.box.com/guides/box-ai/ai-models/meta-muse-spark-1-2-model-card According to Meta, **Meta Muse Spark 1.2** is a multimodal model designed for general-purpose text and image understanding. ## Model details | Item | Value | Description | | - | - | - | | Model name | **Muse Spark 1.2** | The name of the model. | | Model category | **Standard** | The category of the model: Standard, Premium, or Ultra. | | API model name | `meta__muse_spark_1_2` | The name of the model that is used in the Box AI API for model overrides. You must provide this exact name for the API to work. | | Compliance | **N/A** | Government compliance frameworks and authorizations applicable to this model. | | Hosting layer | **Meta** | The trusted organization that securely hosts the LLM. | | Model provider | **Meta** | The organization that provides this model. | | Release date | **August 5th, 2026** | The release date for the model. | | Knowledge cutoff date | **Not specified** | The date after which the model does not receive information updates. | | Input context window | **1m tokens** | The number of tokens supported by the input context window. | | Maximum output tokens | **Not specified** | The number of tokens that can be generated by the model in a single request. | | Empirical throughput | **Not specified** | The number of tokens the model can generate per second. | | Open source | **No** | Specifies whether the model's code is available for public use. | ## Additional documentation For additional information, see [official Meta Muse Spark 1.2 documentation][meta-muse-spark]. [meta-muse-spark]: https://dev.meta.ai/docs/overview#muse-spark # Meta Muse Spark 1.3 Source: https://developer.box.com/guides/box-ai/ai-models/meta-muse-spark-1-3-model-card According to Meta, **Meta Muse Spark 1.3** is a multimodal model designed for general-purpose text and image understanding. ## Model details | Item | Value | Description | | - | - | - | | Model name | **Muse Spark 1.3** | The name of the model. | | Model category | **Standard** | The category of the model: Standard, Premium, or Ultra. | | API model name | `meta__muse_spark_1_3` | The name of the model that is used in the Box AI API for model overrides. You must provide this exact name for the API to work. | | Compliance | **N/A** | Government compliance frameworks and authorizations applicable to this model. | | Hosting layer | **Meta** | The trusted organization that securely hosts the LLM. | | Model provider | **Meta** | The organization that provides this model. | | Release date | **September 2nd, 2026** | The release date for the model. | | Knowledge cutoff date | **Not specified** | The date after which the model does not receive information updates. | | Input context window | **1m tokens** | The number of tokens supported by the input context window. | | Maximum output tokens | **Not specified** | The number of tokens that can be generated by the model in a single request. | | Empirical throughput | **Not specified** | The number of tokens the model can generate per second. | | Open source | **No** | Specifies whether the model's code is available for public use. | ## Additional documentation For additional information, see [official Meta Muse Spark 1.3 documentation][meta-muse-spark]. [meta-muse-spark]: https://dev.meta.ai/models/muse-spark # Ask questions to Box AI Source: https://developer.box.com/guides/box-ai/ai-tutorials/ask-questions Use the POST /ai/ask endpoint to ask questions about one or more files stored in Box. Box AI API allows you to ask a question about a supplied file or a set of files, and get a response based on the content. For example, while viewing a document in Box, you can ask Box AI to summarize the content. You can also ask questions against a Box Hub. When you pass a hub as the item, Box AI searches the hub's indexed content and returns answers grounded in the curated documents the querying user has access to. See Ask questions about a hub below. ## Before you start Make sure you followed the steps listed in getting started with Box AI to create a platform app and authenticate. ## Send a request To send a request containing your question, use the `POST /2.0/ai/ask` endpoint and provide the mandatory parameters. ```sh cURL theme={null} curl -i -L -X POST "https://api.box.com/2.0/ai/ask" \ -H "content-type: application/json" \ -H "authorization: Bearer " \ -d '{ "mode": "single_item_qa", "prompt": "What is the value provided by public APIs based on this document?", "items": [ { "type": "file", "id": "9842787262" } ], "dialogue_history": [ { "prompt": "Make my email about public APIs sound more professional", "answer": "Here is the first draft of your professional email about public APIs", "created_at": "2013-12-12T10:53:43-08:00" } ], "include_citations": true, "ai_agent": { "type": "ai_agent_ask", "long_text": { "model": "openai__gpt_5_mini", "prompt_template": "It is `{current_date}`. Use this content: `{content}`. Answer: `{user_question}`" }, "basic_text": { "model": "openai__gpt_5_mini" } } }' ``` ```typescript Node/TypeScript v10 theme={null} await client.ai.createAiAsk({ mode: 'single_item_qa' as AiAskModeField, prompt: 'which direction sun rises', items: [ { id: fileToAsk.id, type: 'file' as AiItemAskTypeField, content: 'Sun rises in the East', } satisfies AiItemAsk, ], aiAgent: aiAskAgentConfig, } satisfies AiAsk); ``` ```python Python v10 theme={null} client.ai.create_ai_ask( CreateAiAskMode.SINGLE_ITEM_QA, "which direction sun rises", [ AiItemAsk( id=file_to_ask.id, type=AiItemAskTypeField.FILE, content="Sun rises in the East", ) ], ai_agent=ai_ask_agent_config, ) ``` ```cs .NET v10 theme={null} await client.Ai.CreateAiAskAsync(requestBody: new AiAsk(mode: AiAskModeField.SingleItemQa, prompt: "which direction sun rises", items: Array.AsReadOnly(new [] {new AiItemAsk(id: fileToAsk.Id, type: AiItemAskTypeField.File) { Content = "Sun rises in the East" }}))); ``` ```swift Swift v10 theme={null} try await client.ai.createAiAsk(requestBody: AiAsk(mode: AiAskModeField.singleItemQa, prompt: "which direction sun rises", items: [AiItemAsk(id: fileToAsk.id, type: AiItemAskTypeField.file, content: "Sun rises in the East")])) ``` ```java Java v10 theme={null} client.getAi().createAiAsk(new AiAsk.Builder(AiAskModeField.SINGLE_ITEM_QA, "which direction sun rises", Arrays.asList(new AiItemAsk.Builder(fileToAsk.getId(), AiItemAskTypeField.FILE).content("Sun rises in the East").build())).aiAgent(aiAskAgentConfig).build()) ``` ```java Java v5 theme={null} BoxAIResponse response = BoxAI.sendAIRequest( api, "What is the content of the file?", Collections.singletonList("123456", BoxAIItem.Type.FILE), BoxAI.Mode.SINGLE_ITEM_QA ); ``` ```python Python v4 theme={null} items = [{ "id": "1582915952443", "type": "file", "content": "More information about public APIs" }] ai_agent = { 'type': 'ai_agent_ask', 'basic_text_multi': { 'model': 'openai__gpt_3_5_turbo' } } answer = client.send_ai_question( items=items, prompt="What is this file?", mode="single_item_qa", ai_agent=ai_agent ) print(answer) ``` ```cs .NET v6 theme={null} BoxAIResponse response = await client.BoxAIManager.SendAIQuestionAsync( new BoxAIAskRequest { Prompt = "What is the name of the file?", Items = new List() { new BoxAIAskItem() { Id = "12345" } }, Mode = AiAskMode.single_item_qa }; ); ``` ```javascript Node v4 theme={null} client.ai.ask( { prompt: 'What is the capital of France?', items: [ { type: 'file', id: '12345' } ], mode: 'single_item_qa' }) .then(response => { /* response -> { "answer": "Paris", "created_at": "2021-10-01T00:00:00Z", "completion_reason": "done" } */ }); ``` ### Parameters To make a call, you need to pass the following parameters. Mandatory parameters are in **bold**. | Parameter | Description | Available values | Example | | - | - | - | - | | **`mode`** | The type of request. Use `single_item_qa` for a single file (the `items` array must contain exactly one element) or `multiple_item_qa` for up to 25 files. For full details on file size, image, and prompt limits, see input limits. | `single_item_qa`, `multiple_item_qa` | `single_item_qa` | | **`prompt`** | The question about your document or content. Maximum 10,000 characters. | | `What is this document about?` | | `dialogue_history.prompt` | The prompt previously provided by the client and answered by the Large Language Model (LLM). | | `Make my email about public APIs sound more professional` | | `dialogue_history.answer` | The answer previously provided by the LLM. | | `Here is a draft of your professional email about public APIs.` | | `dialogue_history.created_at` | The ISO date formatted timestamp of when the previous answer to the prompt was created. | | `2012-12-12T10:53:43-08:00` | | `include_citations` | Specifies if the citations should be returned in the answer. | `true`, `false` | `true` | | **`items.id`** | The ID of the file or hub you want to provide as input. | | `112233445566` | | **`items.type`** | The type of the provided input. Use `file` for one or more files, or `hubs` to query a Box Hub. A `hubs` item must be the only item in the request. | `file`, `hubs` | `file` | | `items.content` | The content of the item. Usually it is the text representation. | | `An application programming interface (API) is a way for two or more computer programs or components to communicate with each other. It is a type of software interface...` | | `ai_agent` | Override the default model configuration. Lets you change the model, prompt template, system message, or LLM parameters. See the override system for how it works and AI model overrides for examples. | | | ## Use cases ## Ask questions about an item This example shows how to ask a question about one or more items using the `POST ask/ai` API. When using this endpoint, remember to specify the `mode` parameter depending on the number of items you want to supply. ```sh theme={null} curl -i -L -X POST "https://api.box.com/2.0/ai/ask" \ -H "content-type: application/json" \ -H "authorization: Bearer " \ -d '{ "mode": "single_item_qa", "items": [ { "id": "12345678", "type": "file" } ], "prompt": "List the guidelines on creating questions in Box AI for Documents" }' ``` The response will be as follows: ```sh theme={null} { "answer": "The guidelines for working with questions in Box AI for Documents are as follows:\n\n1. Box AI pulls information only from the document loaded in preview.\n2. If questions fall outside the scope of the document, Box AI will inform you that it cannot answer.\n3. Be specific when asking questions; use parameters like numbered lists, brevity, tables, and central themes or key points.\n4. Aim to stay within the scope of the document.\n5. Focus on text-based responses only.", "created_at": "2024-11-04T02:30:09.557-08:00", "completion_reason": "done" } ``` ## Ask questions about a hub Instead of supplying individual files, you can point Box AI at an entire Box Hub and ask questions across all of its curated content. This is useful for knowledge bases such as RFP answer banks, policy libraries, and product documentation portals, where you want natural language answers grounded in an approved set of materials without managing file IDs yourself. To query a hub, set `items.type` to `hubs` and `items.id` to the hub ID. A hub must be supplied as the only item in the request. The example below uses `single_item_qa` mode, which is the pattern shown in the Sales RFP answer bank tutorial. ```sh theme={null} curl -i -L -X POST "https://api.box.com/2.0/ai/ask" \ -H "content-type: application/json" \ -H "authorization: Bearer " \ -d '{ "mode": "single_item_qa", "items": [ { "id": "98765432", "type": "hubs" } ], "prompt": "What is our standard SLA for enterprise support?" }' ``` Box AI searches the hub's indexed content and returns an answer grounded in your approved materials. Because hubs inherit Box permissions, Box AI only references documents the querying user has access to. The hub must have AI features enabled (see Update a hub). For an end-to-end walkthrough that provisions a hub, populates it with content, and queries it with Box AI, see the Build a sales RFP answer bank with Box Hubs and AI tutorial. ## Ask questions with `content` parameter If you use the `content` parameter as the source of input for Box AI, it will use it as the primary source. ```sh theme={null} curl -i -L -X POST "https://api.box.com/2.0/ai/ask" \ -H "content-type: application/json" \ -H "authorization: Bearer " \ -d '{ "mode": "single_item_qa", "items": [ { "id": "12345678", "type": "file", "content": "This is a document about Box AI For documents. It consists of the functionality summary and guidelines on how to work with Box AI. Additionally, it provides a set of best practices for creating questions." } ], "prompt": "List the guidelines on creating questions in Box AI for Documents" }' ``` The response to this request is based on the `content` parameter instead of the file's content: ```sh theme={null} { "answer": "The document does not provide specific guidelines on working with questions in Box AI for Documents. It only mentions that it includes a set of best practices for creating questions, but the details of those guidelines are not included in the text provided. If you have more information or another document, I can help further!", "created_at": "2024-11-04T02:31:51.125-08:00", "completion_reason": "done" } ``` ## Ask questions with `citations` parameter Setting the `citations` parameter to `true` causes the response to include excerpts from source file or files Box AI used to compile the answer. ```sh theme={null} curl -i -L -X POST "https://api.box.com/2.0/ai/ask" \ -H "content-type: application/json" \ -H "authorization: Bearer " \ -d '{ "mode": "multiple_item_qa", "include_citations": true, "items": [ { "id": "12345678", "type": "file" } ], "prompt": "List the guidelines on working with responses in Box AI for Documents" }' ``` The resulting answer includes the source file and direct content citations. ```sh theme={null} { "answer": "The guidelines for working with questions in Box AI for Documents are as follows:\n\n1. Box AI pulls information only from the document loaded in preview, and cannot answer questions outside its scope.\n2. Be specific when asking questions; use parameters like numbered lists, brevity, tables, and central themes or key points.\n3. Examples of better phrasing include asking for a numbered list of key points instead of just \"list key points,\" and requesting a succinct outline of important points rather than a general inquiry about the document's purpose.\n4. Stay within the scope of the document and focus on text-based responses only.", "created_at": "2024-11-04T02:35:00.578-08:00", "completion_reason": "done", "citations": [ { "type": "file", "id": "12345678", "name": "Box AI for Documents.docx", "content": "Guidelines for Box AI questions\nBox AI pulls information only from the document you loaded in preview." }, { "type": "file", "id": "12345678", "name": "Box AI for Documents.docx", "content": "If you ask any questions outside of the scope of the document, Box AI informs you that it cannot answer the question with the information provided." }, { "type": "file", "id": "12345678", "name": "Box AI for Documents.docx", "content": "As you ask Box AI to analyze your document, consider these suggestions:\n· Be as specific as possible." }, { "type": "file", "id": "12345678", "name": "Box AI for Documents.docx", "content": "Box AI for Documents\n\nWhen viewing a document in Box, you can ask Box AI to summarize document content, search key points, and write outline drafts based on your document files." } ] } ``` # Override AI model configuration Source: https://developer.box.com/guides/box-ai/ai-tutorials/default-agent-overrides Step-by-step examples of overriding prompts and models for ask, text_gen, and extract endpoints. ## Before you start Make sure you followed the steps listed in getting started with Box AI to create a platform app and authenticate. To get more context, read about agent overrides. ## Override prompt This example shows how to use the `prompt_template` parameter to change the query result. The first step is to ask Box AI to summarize a document about Box AI for Documents. The `mode` parameter is set to `single_item_qa` because only one document is supplied. ```sh theme={null} curl -i -L -X POST "https://api.box.com/2.0/ai/ask" \ -H "content-type: application/json" \ -H "authorization: Bearer " \ -d '{ "mode": "single_item_qa", "prompt": "Summarize this article about Box AI for Documents", "items": [ { "type": "file", "id": "123467890" } ] }' ``` You will get a response similar to the following: ```sh theme={null} { "answer": "Box AI for Documents is a tool that enhances document analysis by allowing users to summarize content, identify key points, and draft outlines directly from files in Box. It supports various file types, including text documents, spreadsheets, and presentation slides. Users can initiate interactions with Box AI through the web app, where they can select suggestions or type specific questions. Responses are generated in real time, and users have options to save or clear chat history. The document also provides guidelines for effective inquiries and troubleshooting tips for potential issues with using Box AI.", "created_at": "2024-10-08T00:29:07.283-07:00", "completion_reason": "done" } ``` To further improve the result, you can use the `prompt_template` parameter to add some more instructions for Box AI. In this example, let's change the tone of the response. ```sh theme={null} { "prompt": "Summarize this article about Box AI for Documents", "mode": "single_item_qa", "items": [ { "id": "123467890", "type": "file" } ], "ai_agent": { "type": "ai_agent_ask", "basic_text": { "prompt_template": "{user_question} Write the summary in an informal way.{content}" } } } ``` The response would be slightly less formal: ```sh theme={null} { "answer": "Box AI for Documents is a tool that helps you analyze and gain insights from your documents in Box. You can use it to summarize content, identify key points, and draft outlines, making it easier to handle meeting notes, reports, and marketing materials. To get started, just open a file in the Box web app and click the Box AI button. It offers quick suggestions like summarizing the document or checking for next steps. Responses are generated in real time, and you can save them or clear chat history as needed. Just remember, Box AI only pulls info from the document you're viewing, so be specific with your questions!", "created_at": "2024-10-08T00:38:01.767-07:00", "completion_reason": "done" } ``` ## Override AI model (text generation) This example shows you how changing the AI model in the `ai_agent` options can influence the way the text is generated. First let's generate some text using the `POST ai/text_gen` endpoint. This endpoint is using the OpenAI 3.5 turbo model by default. ```sh theme={null} curl -i -L -X POST "https://api.box.com/2.0/ai/text_gen" \ -H "content-type: application/json" \ -H "authorization: Bearer " \ -d '{ "prompt": "Write a short post about Box AI for documents.Make it highlight the benefits of the solution. You can add some emoticons.", "items": [ { "id": "123467890", "type": "file" } ] }' ``` The response is as follows: ```sh theme={null} { "answer": "🌟 Exciting News! 🌟\n\nIntroducing Box AI for documents - your new best friend in creating smarter, more efficient content! 🤖💡\n\n🔹 Say goodbye to manual searching and organizing - Box AI does it all for you!\n🔹 Enjoy lightning-fast document analysis and categorization.\n🔹 Boost productivity with automated suggestions and smart recommendations.\n🔹 Collaborate seamlessly with real-time insights and intelligent tagging.\n\nExperience the future of document creation with Box AI - making work easier, faster, and more fun! 🚀💻 #BoxAI #SmartDocuments", "created_at": "2024-10-08T01:19:06.22-07:00", "completion_reason": "done" } ``` Let's change the model using the `ai_agent` configuration: ```sh theme={null} curl -i -L -X POST "https://api.box.com/2.0/ai/text_gen" \ -H "content-type: application/json" \ -H "authorization: Bearer " \ -d '{ "prompt": "Write a short post about Box AI for documents.Make it highlight the benefits of the solution. You can add some emoticons.", "items": [ { "id": "123467890", "type": "file" } ], "ai_agent": { "type": "ai_agent_text_gen", "basic_gen": { "model": "openai__gpt_5" } } }' ``` After the model switch, the response is slightly different: ```sh theme={null} { "answer": "🚀 **Boost Your Productivity with Box AI for Documents!** 📄✨\n\nSay goodbye to tedious document creation and editing! With Box AI, you can streamline your workflow and focus on what truly matters. Here’s why you’ll love it:\n\n1. **Smart Suggestions** 🤖: Get real-time recommendations to enhance your content.\n2. **Automated Formatting** 📝: Ensure consistency across all your documents effortlessly.\n3. **Collaboration Made Easy** 👥: Work seamlessly with your team, no matter where they are.\n4. **Time-Saving Templates** ⏳: Use pre-built templates to speed up document creation.\n5. **Enhanced Accuracy** ✅: Reduce errors with intelligent proofreading.\n\nTransform the way you work with documents and experience a new level of efficiency with Box AI! 🌟", "created_at": "2024-10-08T01:28:36.777-07:00", "completion_reason": "done" } ``` As you can see the responses differ to some extent. Thanks to the model switch, you can optimize your interaction with Box AI and choose the most suitable model for your needs. ## Override AI model (metadata extraction) Switching models can also give us different results for metadata extraction. Let's use a sample contract to extract the metadata. In this example, the model used is Google Gemini. ```sh theme={null} curl -i -L -X POST 'https://api.box.com/2.0/ai/extract' \ -H 'content-type: application/json' \ -H 'authorization: Bearer ' \ -d '{ "prompt": "Extract any data that would be good metadata to save for future contracts.", "items": [ { "type": "file", "id": "123456789" } ] }' ``` The response is a set of metadata: ```sh theme={null} { "answer": "{\"Buyer Legal Entity Name\": \"Acme Retail Corp.\", \"Supplier Legal Entity Name\": \"Acme Manufacturing Inc.\", \"Buyer Contact Person\": \"Jane Doe\", \"Supplier Contact Person\": \"Eva Smith\", \"Payment Term\": \"payment in full before pickup of goods\", \"Invoice Currency\": \"Euro\", \"Incoterm\": \"FCA Amsterdam\", \"Governing Law\": \"laws state jurisdiction in which supplier is located\", \"Effective Date\": \"March 27, 2024\", \"Buyer Signature Date\": \"March 28th, 2024\", \"Supplier Signature Date\": \"March 28th, 2024\"}", "created_at": "2024-10-08T01:53:14.993-07:00", "completion_reason": "done" } ``` Let's change the model to the most recent OpenAI option: ```sh theme={null} curl -i -L -X POST 'https://api.box.com/2.0/ai/extract' \ -H 'content-type: application/json' \ -H 'authorization: Bearer ' \ -d '{ "prompt": "Extract any data that would be good metadata to save for future contracts.", "items": [ { "type": "file", "id": "123456789" } ], "ai_agent": { "type": "ai_agent_extract", "basic_text": { "model": "openai__gpt_5" } } }' ``` Using this model results in a response listing more metadata entries: ```sh theme={null} { "answer": "{\"Effective Date\": \"March 27, 2024\", \"Supplier Legal Entity Name\": \"Acme Manufacturing Inc.\", \"Supplier Registered Office Address\": \"123 Main Street\", \"Supplier Contact Person(s)\": \"Eva Smith\", \"Buyer Legal Entity Name\": \"Acme Retail Corp.\", \"Buyer Registered Office Address\": \"456 Market Avenue\", \"Buyer Contact Person(s)\": \"Jane Doe\", \"Incoterm\": \"FCA Amsterdam\", \"Payment Term\": \"payment in full before pickup of goods\", \"Invoice Currency\": \"Euro\", \"Buyer Printed Name\": \"Jane Doe\", \"Buyer Date\": \"March 28th, 2024\", \"Buyer Title / Position\": \"CEO\", \"Seller Printed Name\": \"Eve Smith\", \"Seller Date\": \"March 28th, 2024\", \"Seller Title / Position\": \"Sales Manager\"}", "created_at": "2024-10-08T01:54:28.099-07:00", "completion_reason": "done" } ``` # Extract metadata from file (freeform) Source: https://developer.box.com/guides/box-ai/ai-tutorials/extract-metadata Use the POST /ai/extract endpoint to extract metadata from documents using natural language prompts. Box AI API allows you to query a document and extract metadata based on a provided prompt. **Freeform** means that the prompt can include a stringified version of formats such as JSON or XML, or even plain text. The **Extract metadata (freeform)** endpoint doesn't support OCR. To extract metadata from image files (TIFF, PNG, JPEG) or documents in languages other than English, use the Extract metadata (structured) endpoint. ## Before you start Make sure you followed the steps listed in getting started with Box AI to create a platform app and authenticate. ## Send a request To send a request, use the `POST /2.0/ai/extract` endpoint. ```sh cURL theme={null} curl -i -L -X POST 'https://api.box.com/2.0/ai/extract' \ -H 'content-type: application/json' \ -H 'authorization: Bearer ' \ -d '{ "prompt": "Extract data related to contract conditions", "items": [ { "type": "file", "id": "1497741268097" } ], "ai_agent": { "type": "ai_agent_extract", "long_text": { "model": "openai__gpt_5_mini", "prompt_template": "It is `{current_date}`. Use this content: `{content}`. Answer: `{user_question}`" }, "basic_text": { "model": "openai__gpt_5_mini" } } }' ``` ```typescript Node/TypeScript v10 theme={null} await client.ai.createAiExtract({ prompt: 'firstName, lastName, location, yearOfBirth, company', items: [new AiItemBase({ id: file.id })], aiAgent: agentIgnoringOverridingEmbeddingsModel, } satisfies AiExtract); ``` ```python Python v10 theme={null} client.ai.create_ai_extract( "firstName, lastName, location, yearOfBirth, company", [AiItemBase(id=file.id)], ai_agent=agent_ignoring_overriding_embeddings_model, ) ``` ```cs .NET v10 theme={null} await client.Ai.CreateAiExtractAsync(requestBody: new AiExtract(prompt: "firstName, lastName, location, yearOfBirth, company", items: Array.AsReadOnly(new [] {new AiItemBase(id: file.Id)}))); ``` ```swift Swift v10 theme={null} try await client.ai.createAiExtract(requestBody: AiExtract(prompt: "firstName, lastName, location, yearOfBirth, company", items: [AiItemBase(id: file.id)])) ``` ```java Java v10 theme={null} client.getAi().createAiExtract(new AiExtract.Builder("firstName, lastName, location, yearOfBirth, company", Arrays.asList(new AiItemBase(file.getId()))).aiAgent(agentIgnoringOverridingEmbeddingsModel).build()) ``` ```java Java v5 theme={null} BoxAIResponse response = BoxAI.extractMetadataFreeform( api, "firstName, lastName, location, yearOfBirth, company", Collections.singletonList(new BoxAIItem("123456", BoxAIItem.Type.FILE)) ); ``` ### Parameters To make a call, you must pass the following parameters. Mandatory parameters are in **bold**. The `items` array must contain exactly one element. For prompt and file limits, see input limits. | Parameter | Description | Example | | - | - | - | | **`prompt`** | The request for Box AI to extract metadata. Maximum 10,000 characters. | Create a meeting agenda for a weekly sales meeting. | | **`items.id`** | Box file ID of the document. The ID must reference an actual file with an extension. | `1233039227512` | | **`items.type`** | The type of the supplied input. | `file` | | `items.content` | The content of the item, often the text representation. | `This article is about Box AI`. | | `ai_agent` | Override the default model configuration. Lets you change the model, prompt template, system message, or LLM parameters. See the override system for how it works and AI model overrides for examples. | | ## Use cases This example shows you how to extract metadata from a sample invoice. ### Create the request To get the response from Box AI, call `POST /2.0/ai/extract` endpoint with the following parameters: * `prompt` that can be a query, or a structured or unstructured list of fields to extract. * `type` and `id` of the file to extract the data from. ### Create the prompt Depending on the use case and the level of detail, you can construct various prompts. #### Use plain text Because this endpoint allows freeform prompts, you can use plain text to get the information. ```bash theme={null} curl -i -L -X POST 'https://api.box.com/2.0/ai/extract' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data '{ "prompt": "find the document type (invoice or po), vendor, total, and po number", "items": [ { "type": "file", "id": "1443721424754" } ] }' ``` In such a case, the response will be based on the keywords included in the text: ```bash theme={null} { "answer": "{\"Document Type\": \"Invoice\", \"Vendor\": \"Quasar Innovations\", \"Total\": \"$1,050\", \"PO Number\": \"003\"}", "created_at": "2024-05-31T10:30:51.223-07:00", "completion_reason": "done" } ``` #### Use specific terms If you don't want to write the entire sentence, the prompt can consist of terms that you expect to find in an invoice: ```bash theme={null} curl -i -L -X POST 'https://api.box.com/2.0/ai/extract' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data '{ "prompt": "{\"vendor\",\"total\",\"doctype\",\"date\",\"PO\"}", "items": [ { "type": "file", "id": "1443721424754" } ] }' ``` Using this approach results in a list of terms provided in the request and their values: ```bash theme={null} { "answer": "{\"vendor\": \"Quasar Innovations\", \"total\": \"$1,050\", \"doctype\": \"Invoice\", \"PO\": \"003\"}", "created_at": "2024-05-31T10:28:51.906-07:00", "completion_reason": "done" } ``` #### Use key-value pairs The prompt can also be a list of key-value pairs that helps Box AI to come up with the metadata structure. This approach requires listing the key-value pairs within a `fields` array. ```bash theme={null} curl -i -L -X POST 'https://api.box.com/2.0/ai/extract' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data '{ "prompt": "{\"fields\":[{\"key\":\"vendor\",\"displayName\":\"Vendor\",\"type\":\"string\",\"description\":\"Vendorname\"},{\"key\":\"documentType\",\"displayName\":\"Type\",\"type\":\"string\",\"description\":\"\"}]}", "items": [ { "type": "file", "id": "1443721424754" } ] }' ``` The response includes the `fields` present in the file, along with their values: ```bash theme={null} { "answer": "{\"vendor\": \"Quasar Innovations\", \"documentType\": \"Invoice\"}", "created_at": "2024-05-31T10:15:38.17-07:00", "completion_reason": "done" } ``` # Extract metadata from file (structured) Source: https://developer.box.com/guides/box-ai/ai-tutorials/extract-metadata-structured Use the POST /ai/extract_structured endpoint to extract metadata using templates, field definitions, or the Enhanced Extract Agent. With the Box AI API, you can extract file metadata as key-value pairs. As input, you can create a structure with the `fields` parameter, or use an existing metadata template. To learn more about creating templates, see [customizing metadata templates in the Admin Console][templates-console] or use the metadata template API. You can also [autofill metadata in templates][autofill-metadata] with the Standard Extraction Agent or the Enhanced Extraction Agent. ## Supported file formats The endpoint supports the following file formats: * PDF * DOC * DOCX * GDOC * ODT * Box Note * TEXT * RTF * XDW * AS * TIFF * TIF * PNG * JPEG * JPG * WEBP * PPT * PPTX * GSLIDE * GSLIDES * ODP * OTP * XLS * XLSX * XLSM * ODS * CSV * Languages: `.js`, `.py`, `.css`, `.php`, `.sql` * JSON * HTML * XML * MD Box AI applies optical character recognition (OCR) when processing image files and scanned documents. This removes the need to convert images to PDF before extraction. ## Supported languages Box AI can extract metadata from documents in the following languages: * English * Japanese * Chinese * Korean - Cyrillic-based languages (such as Russian, Ukrainian, Bulgarian, and Serbian) You don't need additional configuration to use different languages or image formats. Box AI detects the language and applies OCR when needed. ## Before you start Complete the steps in getting started with Box AI to create a platform app and authenticate. ## Send a request To send a request, use the `POST /2.0/ai/extract_structured` endpoint. ```sh cURL theme={null} curl -i -L -X POST 'https://api.box.com/2.0/ai/extract_structured' \ -H 'content-type: application/json' \ -H 'authorization: Bearer ' \ -d '{ "items": [ { "id": "12345678", "type": "file", "content": "This is file content." } ], "fields": [ { "key": "name", "description": "The name of the person.", "displayName": "Name", "prompt": "The name is the first and last name from the email address.", "type": "string", "options": [ { "key": "First Name" }, { "key": "Last Name" } ] } ], "ai_agent": { "type": "ai_agent_extract_structured", "long_text": { "model": "openai__gpt_5_mini" }, "basic_text": { "model": "openai__gpt_5_mini" } } }' ``` ```typescript Node/TypeScript v10 theme={null} await client.ai.createAiExtractStructured({ fields: [ { key: 'firstName', displayName: 'First name', description: 'Person first name', prompt: 'What is your first name?', type: 'string', } satisfies AiExtractStructuredFieldsField, { key: 'lastName', displayName: 'Last name', description: 'Person last name', prompt: 'What is your last name?', type: 'string', } satisfies AiExtractStructuredFieldsField, { key: 'dateOfBirth', displayName: 'Birth date', description: 'Person date of birth', prompt: 'What is the date of your birth?', type: 'date', } satisfies AiExtractStructuredFieldsField, { key: 'age', displayName: 'Age', description: 'Person age', prompt: 'How old are you?', type: 'float', } satisfies AiExtractStructuredFieldsField, { key: 'hobby', displayName: 'Hobby', description: 'Person hobby', prompt: 'What is your hobby?', type: 'multiSelect', options: [ { key: 'guitar' } satisfies AiExtractStructuredFieldsOptionsField, { key: 'books' } satisfies AiExtractStructuredFieldsOptionsField, ], } satisfies AiExtractStructuredFieldsField, ], items: [new AiItemBase({ id: file.id })], aiAgent: agentIgnoringOverridingEmbeddingsModel, } satisfies AiExtractStructured); ``` ```python Python v10 theme={null} client.ai.create_ai_extract_structured( [AiItemBase(id=file.id)], fields=[ CreateAiExtractStructuredFields( key="firstName", display_name="First name", description="Person first name", prompt="What is your first name?", type="string", ), CreateAiExtractStructuredFields( key="lastName", display_name="Last name", description="Person last name", prompt="What is your last name?", type="string", ), CreateAiExtractStructuredFields( key="dateOfBirth", display_name="Birth date", description="Person date of birth", prompt="What is the date of your birth?", type="date", ), CreateAiExtractStructuredFields( key="age", display_name="Age", description="Person age", prompt="How old are you?", type="float", ), CreateAiExtractStructuredFields( key="hobby", display_name="Hobby", description="Person hobby", prompt="What is your hobby?", type="multiSelect", options=[ CreateAiExtractStructuredFieldsOptionsField(key="guitar"), CreateAiExtractStructuredFieldsOptionsField(key="books"), ], ), ], ai_agent=agent_ignoring_overriding_embeddings_model, ) ``` ```cs .NET v10 theme={null} await client.Ai.CreateAiExtractStructuredAsync(requestBody: new AiExtractStructured(items: Array.AsReadOnly(new [] {new AiItemBase(id: file.Id)})) { Fields = Array.AsReadOnly(new [] {new AiExtractStructuredFieldsField(key: "firstName") { DisplayName = "First name", Description = "Person first name", Prompt = "What is your first name?", Type = "string" },new AiExtractStructuredFieldsField(key: "lastName") { DisplayName = "Last name", Description = "Person last name", Prompt = "What is your last name?", Type = "string" },new AiExtractStructuredFieldsField(key: "dateOfBirth") { DisplayName = "Birth date", Description = "Person date of birth", Prompt = "What is the date of your birth?", Type = "date" },new AiExtractStructuredFieldsField(key: "age") { DisplayName = "Age", Description = "Person age", Prompt = "How old are you?", Type = "float" },new AiExtractStructuredFieldsField(key: "hobby") { DisplayName = "Hobby", Description = "Person hobby", Prompt = "What is your hobby?", Type = "multiSelect", Options = Array.AsReadOnly(new [] {new AiExtractStructuredFieldsOptionsField(key: "guitar"),new AiExtractStructuredFieldsOptionsField(key: "books")}) }}) }); ``` ```swift Swift v10 theme={null} try await client.ai.createAiExtractStructured(requestBody: AiExtractStructured(fields: [AiExtractStructuredFieldsField(key: "firstName", displayName: "First name", description: "Person first name", prompt: "What is your first name?", type: "string"), AiExtractStructuredFieldsField(key: "lastName", displayName: "Last name", description: "Person last name", prompt: "What is your last name?", type: "string"), AiExtractStructuredFieldsField(key: "dateOfBirth", displayName: "Birth date", description: "Person date of birth", prompt: "What is the date of your birth?", type: "date"), AiExtractStructuredFieldsField(key: "age", displayName: "Age", description: "Person age", prompt: "How old are you?", type: "float"), AiExtractStructuredFieldsField(key: "hobby", displayName: "Hobby", description: "Person hobby", prompt: "What is your hobby?", type: "multiSelect", options: [AiExtractStructuredFieldsOptionsField(key: "guitar"), AiExtractStructuredFieldsOptionsField(key: "books")])], items: [AiItemBase(id: file.id)])) ``` ```java Java v10 theme={null} client.getAi().createAiExtractStructured(new AiExtractStructured.Builder(Arrays.asList(new AiItemBase(file.getId()))).fields(Arrays.asList(new AiExtractStructuredFieldsField.Builder("firstName").description("Person first name").displayName("First name").prompt("What is your first name?").type("string").build(), new AiExtractStructuredFieldsField.Builder("lastName").description("Person last name").displayName("Last name").prompt("What is your last name?").type("string").build(), new AiExtractStructuredFieldsField.Builder("dateOfBirth").description("Person date of birth").displayName("Birth date").prompt("What is the date of your birth?").type("date").build(), new AiExtractStructuredFieldsField.Builder("age").description("Person age").displayName("Age").prompt("How old are you?").type("float").build(), new AiExtractStructuredFieldsField.Builder("hobby").description("Person hobby").displayName("Hobby").prompt("What is your hobby?").type("multiSelect").options(Arrays.asList(new AiExtractStructuredFieldsOptionsField("guitar"), new AiExtractStructuredFieldsOptionsField("books"))).build())).aiAgent(agentIgnoringOverridingEmbeddingsModel).build()) ``` ```java Java v5 theme={null} BoxAIExtractMetadataTemplate template = new BoxAIExtractMetadataTemplate("templateKey", "enterprise"); BoxAIExtractStructuredResponse result = BoxAI.extractMetadataStructured( api, Collections.singletonList(new BoxAIItem("123456", BoxAIItem.Type.FILE)), template ); JsonObject sourceJson = result.getSourceJson(); ``` ### Parameters The following parameters apply to this request. Mandatory parameters are in **bold**. The `items` array must contain exactly one element. For prompt and file limits, see input limits. | Parameter | Description | Example | | - | - | - | | **`metadata_template`** | The metadata template containing the fields to extract. For your request to work, you must provide either `metadata_template` or `fields`, but not both. | | | **`metadata_template.type`** | The type of metadata template. | `metadata_template` | | **`metadata_template.scope`** | The scope of the metadata template, which can be `global` or `enterprise`. Global templates are available to any Box enterprise. Enterprise templates are bound to a specific enterprise. | `enterprise` | | **`metadata_template.template_key`** | The name of your metadata template. | `invoice` | | **`items.id`** | The Box file ID of the document. The ID must reference a file with an extension. | `1233039227512` | | **`items.type`** | The type of the supplied input. | `file` | | `ai_agent` | Overrides the default model configuration. You can change the model, prompt template, system message, or LLM parameters. See the override system for how it works and AI model overrides for examples. | | | `include_confidence_score` | A flag to indicate whether to include the confidence score for every extracted field. | `true` | | `include_reference` | A flag to indicate whether to include references for every extracted field. | `true` | | `items.content` | The content of the item, often the text representation. | `This article is about Box AI`. | | `fields.description` | A description of the field. | `The person's name.` | | `fields.displayName` | The display name of the field. | `Name` | | `fields.key` | A unique identifier for the field. | `name` | | `fields.namespace` | The namespace of the taxonomy source. Required when you use a `taxonomy` field from an existing taxonomy. | `string` | | `fields.options` | A list of options for this field. This is most often used in combination with the `enum` and `multiSelect` field types. | `[{"key":"First Name"},{"key":"Last Name"}]` | | `fields.options.key` | A unique identifier for the option. | `First Name` | | `fields.prompt` | Additional context about the key (identifier) that can include how to find and format it. | `Name is the first and last name from the email address` | | `fields.type` | The type of the field. Types include, but are not limited to, `string`, `float`, `date`, `enum`, `multiSelect`, `struct`, and `table`. | `string` | | `fields.taxonomy_key` | The identifier for a taxonomy, which corresponds to the `key` of the taxonomy source. Required when you use a `taxonomy` field. | `string` | ### `struct` and `table` field types The Box AI `extract_structured` API supports two complex field types, `struct` and `table`, in addition to the scalar types (`string`, `float`, `date`, `enum`, and `multiSelect`). The `struct` and `table` types enable you to extract grouped and repeating structured data from documents. For best results, use the [Enhanced Extract Agent](/guides/box-ai/ai-tutorials/extract-metadata-structured#enhanced-extract-agent). #### `struct` field type Use the `struct` type to group multiple related sub-fields into a single named JSON object. This is useful when you want to extract a set of related values that belong together and receive them as one structured object rather than separate flat fields. For example, use it for an address or a person's contact details. A `struct` field requires a `fields` array that defines its sub-fields. Each sub-field is an object with the following properties: * `key`: The unique identifier for the sub-field. * `type`: The type of the sub-field. Supported types are `string`, `text`, `number`, `float`, `boolean`, `date`, `enum`, `multiSelect`, and `array[]` (for example, `array[string]`). Nested `struct` or `table` types are not supported as sub-fields. * `displayName`: The display name of the sub-field. * `description`: A description of the sub-field. * `prompt`: Additional context about the sub-field that can include how to find and format it. You can add a prompt at the `struct` field level when instructions apply to the whole grouped object. The output is a single JSON object containing the extracted sub-field values. Example request for the `struct` field type: ```json theme={null} { "fields": [ { "key": "address", "displayName": "Address", "type": "struct", "fields": [ { "key": "street_name", "type": "string" }, { "key": "home_number", "type": "string" }, { "key": "postal_code", "type": "string" }, { "key": "city", "type": "string" } ] } ] } ``` Response: ```json theme={null} { "answer": { "address": { "street_name": "Main St", "home_number": "123", "postal_code": "94105", "city": "San Francisco" } } } ``` #### `table` field type Use the `table` type to extract repeating rows of structured data as an array of JSON objects, where each object represents one row. This is useful when a document contains multiple instances of the same data structure, for example, line items in an invoice or entries in a tax table. A `table` field requires a `fields` array that defines the columns (sub-fields) of each row. The sub-field properties and supported types are identical to those of `struct`. Table extraction is not limited to visually formatted tables. The `table` type extracts repeating data from a grid, key-value pairs, a form layout, or plain prose. The output is an array of JSON objects, where each object represents one extracted row. Example request for the `table` field type: ```json theme={null} { "fields": [ { "key": "line_items", "displayName": "Line Items", "type": "table", "fields": [ { "key": "description", "type": "string" }, { "key": "quantity", "type": "float" }, { "key": "amount", "type": "float" } ] } ] } ``` Response: ```json theme={null} { "answer": { "line_items": [ { "description": "Desk", "quantity": 2.0, "amount": 399.99 }, { "description": "Chair", "quantity": 4.0, "amount": 149.99 } ] } } ``` #### Supported sub-field types Both `struct` and `table` fields support the following types. | Type | Notes | | - | - | | `string` | Scalar or array\[string] | | `text` | Scalar or array\[text] | | `number` | Scalar or array\[number] | | `float` | Scalar or array\[float] | | `boolean` | Scalar or array\[boolean] | | `date` | Scalar or array\[date] | | `enum` | Single occurrence only | | `multiSelect` | Single occurrence only | Nested `struct` and `table` types are not supported as sub-fields. See the `struct` and `table` field types in action. Extract grouped vendor details and a repeating delivery schedule from a supplier agreement, then map the result to a downstream procurement record. ### `include_confidence_score` parameter The `include_confidence_score` parameter returns confidence scores for each extracted field. These scores show how likely it is that the extracted information is correct. Box provides both a number score and a simple rating (Low, Medium, or High) so you and automated tools can quickly see how reliable the results are. To calculate confidence, Box generates multiple answers for the same request in slightly different ways (such as changing settings, rephrasing prompts, or rearranging fields) and evaluates the consistency and frequency of the returned values. Box then turns these signals into probabilities that reflect actual accuracy. * High confidence (0.90 or higher) means the value is very likely correct, and little or no human review is needed. * Medium confidence (0.70 – 0.89) means the value is likely correct, and a quick check is recommended, depending on your risk tolerance. * Low confidence (below 0.70) means the value is uncertain, and a human review is strongly recommended. ### `include_reference` parameter The `include_reference` parameter returns supporting citations and bounding boxes for extracted values. These help you validate extracted values against the source document. A **reference** is a short text excerpt from the source file that supports an extracted value. A **bounding box** identifies the region on the page where that supporting text is located. Box AI calculates bounding boxes from the page-based OCR representation and returns them only for [files supported by OCR](/guides/box-ai/index#ocr-and-file-format-support). To include references in the response, set `include_reference` to `true`. ```json theme={null} { "items": [...], "fields": [...], "include_reference": true } ``` #### Response structure The response includes: * `answer`: The extracted metadata fields. * `references`: Supporting citations for each extracted field. Each reference contains: * `item_id`: The source file ID. * `page`: The zero-indexed page number. * `bounding_box`: The location of the cited text, when available. * `text`: The cited text excerpt. #### Response schema example ```json theme={null} { "answer": { "vendor": "Quasar Innovations", "document_total": 1050, "document_type": "Purchase Order" }, "references": { "document_type": [ { "item_id": "314", "page": 0, "bounding_box": { "left": 0.1, "top": 0.1, "right": 0.3, "bottom": 0.5 }, "text": "Vendor Company: Acme" } ], "contract": [ { "item_id": "314", "page": 0, "bounding_box": { "left": 0.1, "top": 0.1, "right": 0.3, "bottom": 0.5 }, "text": "For: $1234" } ], "start_date": [ { "item_id": "314", "page": 0, "bounding_box": { "left": 0.1, "top": 0.1, "right": 0.3, "bottom": 0.5 }, "text": "Contracted on: 2023-12-02" } ], "services": [ [ { "item_id": "314", "page": 1, "bounding_box": { "left": 0.1, "top": 0.1, "right": 0.3, "bottom": 0.5 }, "text": "Cloud Computing" } ], [ { "item_id": "314", "page": 1, "bounding_box": { "left": 0.1, "top": 0.1, "right": 0.3, "bottom": 0.5 }, "text": "Data Analytics" } ], [ { "item_id": "314", "page": 1, "bounding_box": { "left": 0.1, "top": 0.1, "right": 0.3, "bottom": 0.5 }, "text": "Technical Support" } ] ] } } ``` #### Bounding box format A bounding box is an object with four normalized coordinates: * `left`: The normalized x-coordinate of the left edge of the bounding box. * `top`: The normalized y-coordinate of the top edge of the bounding box. * `right`: The normalized x-coordinate of the right edge of the bounding box. * `bottom`: The normalized y-coordinate of the bottom edge of the bounding box. These values range from 0 to 1, where (0,0) is the top-left corner and (1,1) is the bottom-right corner of the page. For example: ```json theme={null} { "left": 0.20, "top": 0.10, "right": 0.60, "bottom": 0.30 } ``` Every extracted metadata field has an associated list of references. For multi-value fields, each extracted value has its own list of references, resulting in a nested list structure. `bounding_box` is optional and can be `null`. ## Use cases This example shows you how to extract metadata from a sample invoice. Extract the vendor name, invoice number, and a few more details. Invoice for Quasar Innovations with a vendor, invoice number, purchase order number, two line items, and a total of $1,050 ### Create the request To get the response from Box AI, call the `POST /2.0/ai/extract_structured` endpoint with the following parameters: * `items.type` and `items.id` to specify the file to extract data from * `fields` to specify the data to extract from the file * `metadata_template` to supply an existing metadata template You can use either `fields` or `metadata_template` to specify your structure, but not both. ### Use `fields` parameter The `fields` parameter enables you to specify the data you want to extract. Each `fields` object includes parameters you can use to describe that data. For example, you can add the field type, description, or a prompt with additional context. ```bash theme={null} curl -i -L -X POST 'https://api.box.com/2.0/ai/extract_structured' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data '{ "items": [ { "id": "1517628697289", "type": "file" } ], "fields": [ { "key": "document_type", "type": "enum", "prompt": "What type of document is this?", "options": [ { "key": "Invoice" }, { "key": "Purchase Order" }, { "key": "Unknown" } ] }, { "key": "document_date", "type": "date" }, { "key": "vendor", "description": "The name of the entity.", "prompt": "Which vendor is sending this document?", "type": "string" }, { "key": "document_total", "type": "float" } ] }' ``` The response lists the specified fields and their values: ```json theme={null} { "document_date": "2024-02-13", "vendor": "Quasar Innovations", "document_total": 1050, "document_type": "Purchase Order" } ``` ### Use metadata template If you prefer to use a metadata template, you can provide its `template_key`, `type`, and `scope`. ```bash theme={null} curl -i -L -X POST 'https://api.box.com/2.0/ai/extract_structured' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data '{ "items": [ { "id": "1517628697289", "type": "file" } ], "metadata_template": { "template_key": "rbInvoicePO", "type": "metadata_template", "scope": "enterprise_1134207681" } }' ``` The response lists the fields included in the metadata template and their values: ```json theme={null} { "documentDate": "February 13, 2024", "total": "$1050", "documentType": "Purchase Order", "vendor": "Quasar Innovations", "purchaseOrderNumber": "003" } ``` ### Enhanced Extract Agent To use the Enhanced Extract Agent, specify the `ai_agent` object as follows: ```json theme={null} { "ai_agent": { "type": "ai_agent_id", "id": "enhanced_extract_agent" } } ``` To extract data using the Enhanced Extract Agent, you need one of the following: * [Inline field definitions][inline-field] (best when fields change frequently) * [Metadata template][metadata-template] (best when fields stay consistent) See the following sample, which uses the Box Python SDK: ```python theme={null} from box_sdk_gen import ( AiAgentReference, AiAgentReferenceTypeField, AiItemBase, AiItemBaseTypeField, BoxClient, BoxCCGAuth, CCGConfig, CreateAiExtractStructuredMetadataTemplate ) # Create your client credentials grant config from the Developer Console ccg_config = CCGConfig( client_id="my_box_client_id", # replace with your client ID client_secret="my_box_client_secret", # replace with your client secret user_id="my_box_user_id", # replace with the Box user ID that has access # to the file you are referencing ) auth = BoxCCGAuth(config=ccg_config) client = BoxClient(auth=auth) # Create the agent config referencing the Enhanced Extract Agent enhanced_extract_agent_config = AiAgentReference( id="enhanced_extract_agent", type=AiAgentReferenceTypeField.AI_AGENT_ID ) # Use the Box SDK to call the extract_structured endpoint box_ai_response = client.ai.create_ai_extract_structured( # Create the items array containing the file information to extract from items=[ AiItemBase( id="my_box_file_id", # replace with the file ID type=AiItemBaseTypeField.FILE ) ], # Reference the Box metadata template metadata_template=CreateAiExtractStructuredMetadataTemplate( template_key="InvoicePO", scope="enterprise" ), # Attach the agent config you created earlier ai_agent=enhanced_extract_agent_config, ) print(f"box_ai_response: {box_ai_response.answer}") ``` [templates-console]: https://support.box.com/hc/en-us/articles/360044194033-Customizing-Metadata-Templates [changelog]: /changelog [blog]: https://medium.com/box-developer-blog [inline-field]: #use-fields-parameter [metadata-template]: #use-metadata-template [autofill-metadata]: https://support.box.com/hc/en-us/articles/360044196173-Using-Metadata#h_01JJSRYKDKXHGJT9ZHCW1E9RX5 See structured extraction in action. Build an end-to-end automation that watches a folder, extracts invoice fields, and writes metadata back to each file. # Extract APIs overview and use cases Source: https://developer.box.com/guides/box-ai/ai-tutorials/extract-use-cases Explore Box AI Extract use cases including structured and freeform metadata extraction, and how to run extraction jobs across many documents in parallel with one file per request. Data extraction can be challenging because source content varies widely with different layouts, templates, and file types. The Box Extract API provides two AI-powered endpoints: structured extraction and freeform extraction that standardize how you extract metadata from files in Box. With these endpoints, you can: * Extract specific fields into a consistent schema or Box metadata template (structured extraction). * Use enhanced extraction for more complex use cases and improved accuracy powered by advanced AI models (structured extraction). * Extract content when the target fields are not known ahead of time (freeform extraction). * Extract from files the caller can access, without a separate permission system around the model. * Extract data from documents in various languages, file formats, scanned documents, or photos. To explore the possibilities of these endpoints, discover the following use cases in various industries: **Recommended endpoint:** structured metadata extraction (`POST /2.0/ai/extract_structured`) Ideal for high-volume, standardized documents where you need predictable data types: * **Automated data entry:** Structured metadata extraction ensures a consistent JSON response every time thanks to a preconfigured metadata template. * **Invoices and purchase orders:** Extract line items, totals, and dates. * **Client contracts:** Parse standardized fields like "Effective Date" or "Total Contract Value" to update CRM records. **Recommended endpoint:** freeform metadata extraction (`POST /2.0/ai/extract`) Ideal for queries where document structure is not known ahead of time or varies: * **HR onboarding:** Extract key personal details from diverse offer letters or candidate resumes. * **NDA clauses:** Extract specific NDA clauses from legal documents. Use advanced features like Optical Character Recognition (OCR) and enhanced extraction agent for regulated sectors: * **Know Your Customer (KYC) documents:** Verify user’s identity by extracting text from scanned passports or driver's licenses. * **Loan origination:** Automate income verification by pulling data from scanned utility bills or pay stubs. * **Clinical Trial Enrollment:** Extract patient criteria from medical forms to match candidates with trials. * **Regulatory submissions:** Organize and validate the data required for submissions such as FDA or EMA. * **Permit applications:** Accelerate zoning approvals by validating required documentation. * **Public records requests:** Automatically classify and prioritize documents for public requests such as Freedom of Information Act (FOIA). ## Core benefits of using the Box Extract API * **Managed scaling:** The Box Extract API is designed to handle queues and rate limits, so you can run high-volume processing. * **No defensive code:** The Box Extract API is model-agnostic, which enables switching between supported LLMs with minimal code changes and minimizing vendor lock-in. * **Security and compliance:** All extracted data inherits the enterprise-grade security and governance policies of Box. ## Run extraction across thousands of documents There is no batch extract endpoint. `POST /2.0/ai/extract` and `POST /2.0/ai/extract_structured` accept exactly one file per request. To process a large repository, list the files and call extract per file, up to your rate limits: 1. Watch an inbox folder with a webhook (`FILE.UPLOADED`), poll folder items, or start from a Box Automate workflow. 2. Use an identity that is collaborated on those files. Extraction does not bypass Box permissions. 3. For each file ID, call `POST /2.0/ai/extract_structured` (or `POST /2.0/ai/extract` for freeform). Run many requests concurrently. 4. If the API returns `429 Too Many Requests` with a `Retry-After` header, wait a moment and retry. See Box API rate limits (allows around 1000 general requests per minute per user, plus enterprise licensing). 5. Store the JSON on the file as a metadata instance so downstream search and workflows can use the fields. Use Box Automate when you want extraction as a workflow step with branching and human review. Webhook on a folder, one structured extract per PDF, metadata write-back — the same fan-out pattern at a smaller scale. Structured extract with `struct` and `table` fields for complex contracts. Repeat per file for a contract library. ## Next steps Get started with the Box Extract API with practical example-led quick start guides, API reference pages, and extensive developer guides: # Generate text with Box AI Source: https://developer.box.com/guides/box-ai/ai-tutorials/generate-text Use the POST /ai/text_gen endpoint to generate or refine text based on file content stored in Box. You can use Box AI to generate text based on provided content. For example, you can ask Box AI to generate a template based on the content you read or create in Box Notes. Then you can embed the generated text directly into your document. ## Before you start Make sure you followed the steps listed in getting started with Box AI to create a platform app and authenticate. ## Send a request To send a request, use the `POST /2.0/ai/text_gen` endpoint. ```sh cURL theme={null} curl -i -L -X POST "https://api.box.com/2.0/ai/text_gen" \ -H "content-type: application/json" \ -H "authorization: Bearer " \ -d '{ "prompt": "Write a social media post about protein powder.", "items": [ { "id": "12345678", "type": "file", "content": "More information about protein powders" } ], "dialogue_history": [ { "prompt": "Can you add some more information?", "answer": "Public API schemas provide necessary information to integrate with APIs...", "created_at": "2013-12-12T11:20:43-08:00" } ], "ai_agent": { "type": "ai_agent_text_gen", "basic_gen": { "model": "openai__gpt_5_mini" } } }' ``` ```typescript Node/TypeScript v10 theme={null} await client.ai.createAiTextGen({ prompt: 'Parapharse the document.s', items: [ new AiTextGenItemsField({ id: fileToAsk.id, type: 'file' as AiTextGenItemsTypeField, content: 'The Earth goes around the sun. Sun rises in the East in the morning.', }), ], dialogueHistory: [ { prompt: 'What does the earth go around?', answer: 'The sun', createdAt: dateTimeFromString('2021-01-01T00:00:00Z'), } satisfies AiDialogueHistory, { prompt: 'On Earth, where does the sun rise?', answer: 'East', createdAt: dateTimeFromString('2021-01-01T00:00:00Z'), } satisfies AiDialogueHistory, ], aiAgent: aiTextGenAgentConfig, } satisfies AiTextGen); ``` ```python Python v10 theme={null} client.ai.create_ai_text_gen( "Parapharse the document.s", [ CreateAiTextGenItems( id=file_to_ask.id, type=CreateAiTextGenItemsTypeField.FILE, content="The Earth goes around the sun. Sun rises in the East in the morning.", ) ], dialogue_history=[ AiDialogueHistory( prompt="What does the earth go around?", answer="The sun", created_at=date_time_from_string("2021-01-01T00:00:00Z"), ), AiDialogueHistory( prompt="On Earth, where does the sun rise?", answer="East", created_at=date_time_from_string("2021-01-01T00:00:00Z"), ), ], ai_agent=ai_text_gen_agent_config, ) ``` ```cs .NET v10 theme={null} await client.Ai.CreateAiTextGenAsync(requestBody: new AiTextGen(prompt: "Parapharse the document.s", items: Array.AsReadOnly(new [] {new AiTextGenItemsField(id: fileToAsk.Id, type: AiTextGenItemsTypeField.File) { Content = "The Earth goes around the sun. Sun rises in the East in the morning." }})) { DialogueHistory = Array.AsReadOnly(new [] {new AiDialogueHistory() { Prompt = "What does the earth go around?", Answer = "The sun", CreatedAt = Utils.DateTimeFromString(dateTime: "2021-01-01T00:00:00Z") },new AiDialogueHistory() { Prompt = "On Earth, where does the sun rise?", Answer = "East", CreatedAt = Utils.DateTimeFromString(dateTime: "2021-01-01T00:00:00Z") }}) }); ``` ```swift Swift v10 theme={null} try await client.ai.createAiTextGen(requestBody: AiTextGen(prompt: "Parapharse the document.s", items: [AiTextGenItemsField(id: fileToAsk.id, type: AiTextGenItemsTypeField.file, content: "The Earth goes around the sun. Sun rises in the East in the morning.")], dialogueHistory: [AiDialogueHistory(prompt: "What does the earth go around?", answer: "The sun", createdAt: try Utils.Dates.dateTimeFromString(dateTime: "2021-01-01T00:00:00Z")), AiDialogueHistory(prompt: "On Earth, where does the sun rise?", answer: "East", createdAt: try Utils.Dates.dateTimeFromString(dateTime: "2021-01-01T00:00:00Z"))])) ``` ```java Java v10 theme={null} client.getAi().createAiTextGen(new AiTextGen.Builder("Parapharse the document.s", Arrays.asList(new AiTextGenItemsField.Builder(fileToAsk.getId()).type(AiTextGenItemsTypeField.FILE).content("The Earth goes around the sun. Sun rises in the East in the morning.").build())).dialogueHistory(Arrays.asList(new AiDialogueHistory.Builder().prompt("What does the earth go around?").answer("The sun").createdAt(dateTimeFromString("2021-01-01T00:00:00Z")).build(), new AiDialogueHistory.Builder().prompt("On Earth, where does the sun rise?").answer("East").createdAt(dateTimeFromString("2021-01-01T00:00:00Z")).build())).aiAgent(aiTextGenAgentConfig).build()) ``` ```java Java v5 theme={null} List dialogueHistory = new ArrayList<>(); dialogueHistory.add( new BoxAIDialogueEntry( "Make my email about public APIs sound more professional", "Here is the first draft of your professional email about public APIs.", BoxDateFormat.parse("2013-05-16T15:26:57-07:00") ) ); BoxAIResponse response = BoxAI.sendAITextGenRequest( api, "Write an email to a client about the importance of public APIs.", Collections.singletonList(new BoxAIItem("123456", BoxAIItem.Type.FILE)), dialogueHistory ); ``` ```python Python v4 theme={null} items = [{ "id": "1582915952443", "type": "file", "content": "More information about public APIs" }] dialogue_history = [{ "prompt": "Make my email about public APIs sound more professional", "answer": "Here is the first draft of your professional email about public APIs", "created_at": "2013-12-12T10:53:43-08:00" }, { "prompt": "Can you add some more information?", "answer": "Public API schemas provide necessary information to integrate with APIs...", "created_at": "2013-12-12T11:20:43-08:00" }] ai_agent = { 'type': 'ai_agent_text_gen', 'basic_gen': { 'model': 'openai__gpt_3_5_turbo_16k' } } answer = client.send_ai_text_gen( dialogue_history=dialogue_history, items=items, prompt="Write an email to a client about the importance of public APIs.", ai_agent=ai_agent ) print(answer) ``` ```cs .NET v6 theme={null} BoxAIResponse response = await client.BoxAIManager.SendAITextGenRequestAsync( new BoxAITextGenRequest { Prompt = "What is the name of the file?", Items = new List() { new BoxAITextGenItem() { Id = "12345" } }, DialogueHistory = new List() { new BoxAIDialogueHistory() { Prompt = "What is the name of the file?", Answer = "MyFile", CreatedAt = DateTimeOffset.Parse("2024-05-16T15:26:57-07:00") } new BoxAIDialogueHistory() { Prompt = "What is the size of the file?", Answer = "10kb", CreatedAt = DateTimeOffset.Parse("2024-05-16T15:26:57-07:00") } } }; ); ``` ```javascript Node v4 theme={null} client.ai.textGen( { prompt: 'What is the capital of France?', items: [ { type: 'file', id: '12345' } ], dialogue_history: [ { prompt: 'What is the capital of France?', answer: 'Paris', created_at: '2021-10-01T00:00:00Z' }, { prompt: 'What is the capital of Germany?', answer: 'Berlin', created_at: '2021-10-01T00:00:00Z' } ] }) .then(response => { /* response -> { "answer": "The capital of France is Paris.", "created_at": "2021-10-01T00:00:00Z", "completion_reason": "done" } */ }); ``` ### Parameters To make a call, you must pass the following parameters. Mandatory parameters are in **bold**. The `items` array must contain exactly one element. For full details on prompt and file limits, see input limits. | Parameter | Description | Example | | - | - | - | | **`prompt`** | The request for Box AI to generate or refine the text. Maximum 10,000 characters. | Create a meeting agenda for a weekly sales meeting. | | **`items.id`** | Box file ID of the document. | `1233039227512` | | **`items.type`** | The type of the supplied input. | `file` | | `items.content` | The content of the item, often the text representation. | `This article is about Box AI`. | | `dialogue_history.prompt` | The prompt previously provided by the client and answered by the Large Language Model (LLM). | `Make my email about public APIs sound more professional` | | `dialogue_history.answer` | The answer previously provided by the LLM. | `Here is a draft of your professional email about public APIs.` | | `dialogue_history.created_at` | The ISO date formatted timestamp of when the previous answer to the prompt was created. | `2012-12-12T10:53:43-08:00` | | `ai_agent` | Override the default model configuration. Lets you change the model, prompt template, system message, or LLM parameters. See the override system for how it works and AI model overrides for examples. | | ## Use cases Generate text based on the provided file content and a prompt. ```sh theme={null} curl -i -L -X POST "https://api.box.com/2.0/ai/text_gen" \ -H "content-type: application/json" \ -H "authorization: Bearer " \ -d '{ "items": [ { "id": "12345678", "type": "file" } ], "prompt": "Create a short blog post that provides information on Box AI for Documents and focuses on best practices for asking questions. You can add emoticons, but not too many." }' ``` The result will be as follows: ```sh theme={null} { "answer": "📝 **Box AI for Documents: Best Practices for Asking Questions** 🤔\n\n---\n\nWelcome to our blog post on Box AI for Documents! 🎉 Today, we're going to dive into some best practices when it comes to asking questions within this innovative platform.\n\n1. **Be Clear and Concise**: When formulating a question in Box Notes, make sure your query is clear and to the point. This helps Box AI understand exactly what you're looking for.\n\n2. **Provide Context**: Giving context around your question can significantly improve the accuracy of the response generated by Box AI. Include relevant details or background information.\n\n3. **Use Keywords**: Utilize keywords related to your query within the question itself. This can help Box AI better identify the main topic of your inquiry.\n\n4. **Avoid Ambiguity**: Try to avoid vague or ambiguous questions that could lead to misunderstandings. The more precise you are, the better Box AI can assist you.\n\n5. **Review Suggestions Carefully**: After receiving suggestions from Box AI, take the time to review them carefully before incorporating them into your document. Ensure they align with your intended message.\n\nBy following these best practices, you can maximize the effectiveness of Box AI for Documents and streamline your workflow like never before! 💼✨\n\nStay tuned for more tips and tricks on leveraging technology for enhanced productivity! 👩‍💻🚀", "created_at": "2024-11-04T02:46:23.459-08:00", "completion_reason": "done" } ``` # Get started with Box AI Source: https://developer.box.com/guides/box-ai/ai-tutorials/prerequisites Create a platform app, enable the AI scope, and generate a developer token to start using the Box AI API. To implement Box AI API in your solutions, you need to make sure you have access to the functionality. You will also need a platform application with enabled Box AI scope, and a developer token to authenticate your calls. A free developer account gives you access to the Box AI API. Try document summarization, question answering, and metadata extraction through the API. To use Box AI API, make sure it is enabled by an admin in the Admin Console. If you want to use the Box AI APIs in your sandbox, request access from the Box AI team using [this form][form]. ## Create a platform application First you need to create a platform application you will use to make calls. To create an application, follow the guide on creating platform apps. ## Enable Box AI API access To interact with Box AI API, you need the `ai.readwrite` scope added for your application. Before you add the scope, make sure that the Box Admin has granted you the access to Box AI API. If you can't see the **Manage AI** option in your app configuration settings, contact your admin. To add a scope: 1. Open your application in Developer Console. 2. Go to **Configuration** > **Required Access Scopes** > **Content Actions**. 3. Select the **Manage AI** scope. Box automatically includes the scope when making the call. If you are added as a collaborator for a given app, but do not have Box AI API access, you see the **Manage AI** scope checked and grayed out. This means the app owner has the AI scope enabled but you cannot change this setting. box ai scopes 4. Submit your app for authorization or enablement. If you want to enable Box AI API for an existing application, you must re-authorize it. ## Generate a developer token You need a developer token to authenticate your app when sending requests. To generate a token: 1. Go to **Developer Console** > **My Platform Apps**. 2. Click the **Options menu** button (…) on the right. 3. Select **Generate Developer Token**. The token will be automatically generated and saved to clipboard. generate token You can also open your app, go to **Configuration** > **Developer Token** and generate the token. A developer token is only valid for one hour. For additional details, see developer token. After you generate the token, you can use it in cURL or other clients, such as Postman, to make calls. [oauthscopes]: /guides/api-calls/permissions-and-errors/scopes#scopes-oauth-2-authorization [form]: https://forms.gle/Nsh3TwM3W8qg4U35A # Box AI Source: https://developer.box.com/guides/box-ai/index Ask questions, extract metadata, and summarize files stored in Box with the Box AI API. Covers request handling, input limits, and agent overrides. Use the Box AI API to ask questions, extract metadata, and summarize files stored in Box. These guides cover how Box AI processes requests, what limits apply to each endpoint, and how to control model behavior with agent overrides. For capabilities, quick starts, and reference links, see the [Box AI API](/ai/box-ai-api) page. A free developer account gives you access to the Box AI API, Developer Console, and everything you need to start building. ## How requests are processed When you send a request to a Box AI endpoint, Box handles the model infrastructure for you. Your request flows through the following stages: * **File retrieval**: Box reads the file content from the `items` array you provide. If you include the optional `content` parameter, that text is used as the primary input instead of the file's stored content. For `POST /ai/ask`, an `items` entry can also be a [Box Hub](/guides/hubs-api/index) (`"type": "hubs"`), in which case Box searches the hub's indexed content instead of a single file. See [Ask questions about a hub](/guides/box-ai/ai-tutorials/ask-questions#ask-questions-about-a-hub). * **Representation generation**: For text-based files, Box converts the document into a text representation. For images, Box applies OCR automatically on supported endpoints. * **Model routing**: Box routes the request to the default model for that endpoint and mode. You can override this with the `ai_agent` parameter. * **Response generation**: The LLM processes your prompt against the file content and returns a result. Box handles token windowing for long documents (splitting content into chunks with embeddings for `long_text` configurations). Box AI does not support multi-modal requests. If you send both images and text in the same request, only the text is processed. ## Input limits The limits below apply across Box AI endpoints. Exceeding these limits does not produce an error in most cases; Box truncates to the limit and processes what it can. ### Text and prompt limits | Constraint | Limit | | - | - | | Prompt length | 10,000 characters | | Single file text representation (`single_item_qa`) | 2 MB of text. Content beyond 2 MB is truncated. | | Multiple files (`multiple_item_qa`) | Up to 25 files | | Items array for `text_gen` | Exactly 1 file | | Items array for `extract` and `extract_structured` | Exactly 1 file | To process many documents, call extract once per file and run them in parallel. See Run extraction across thousands of documents. ### Image limits | Constraint | Limit | | - | - | | Resolution | 1024 x 1024 pixels | | Maximum images or pages per request | 5. If more are provided, only the first 5 are processed. | ### OCR and file format support OCR is **not** available on all endpoints. | Endpoint | OCR | Supported file formats | | - | - | - | | `POST /ai/text_gen` | No | Text-based files | | `POST /ai/extract` | No | Text-based files | | `POST /ai/extract_structured` | Yes (automatic) | PDF, TIFF, PNG, JPEG | ### Language support Box AI works in English, Japanese, French, Spanish, and many other languages. However, the underlying models are primarily trained on English, so prompts in other languages may return lower quality results. The `extract_structured` endpoint has explicit multilingual support for: * English, Japanese, Chinese, Korean * Cyrillic-based languages (Russian, Ukrainian, Bulgarian, Serbian) Switch the language to Japanese to get better results for this language. ## The `ai_agent` override system The following Box AI endpoints accept an optional `ai_agent` parameter that lets you override the default model configuration: `POST /ai/ask`, `POST /ai/text_gen`, `POST /ai/extract`, and `POST /ai/extract_structured`. This is how you control which LLM runs, how it behaves, and what instructions it receives. ### When to use overrides * **Pinning a model version**: Box updates default models regularly. If your downstream process depends on consistent output, pin to a specific model to avoid unexpected changes. * **Switching models**: Different models produce different results. You can switch to any model in the supported models list to optimize for your use case. * **Customizing prompts**: The `prompt_template` and `system_message` parameters let you steer the LLM's behavior without changing your application code. * **Tuning creativity**: Adjust `temperature` and other `llm_endpoint_params` to control how deterministic or creative the output is. ### How it works Call `GET /2.0/ai_agent_default` with the `mode` you want (`ask`, `text_gen`, `extract`, or `extract_structured`) to retrieve the current defaults. Change the fields you need: `model`, `prompt_template`, `system_message`, `llm_endpoint_params`, or `num_tokens_for_completion`. Leave other fields unchanged. Include the modified configuration as the `ai_agent` parameter in your `POST` request. Box uses your overrides for that request only. ### Configuration structure by endpoint The `ai_agent` object structure varies by endpoint because each handles content differently: | Endpoint | Agent type | Configuration keys | | - | - | - | | `POST /ai/ask` | `ai_agent_ask` | `basic_text`, `basic_text_multi`, `long_text`, `long_text_multi` | | `POST /ai/text_gen` | `ai_agent_text_gen` | `basic_gen` | | `POST /ai/extract` | `ai_agent_extract` | `basic_text`, `long_text` | | `POST /ai/extract_structured` | `ai_agent_extract_structured` | `basic_text`, `long_text` | The `ask` endpoint has four configuration keys because it handles both single-item and multi-item modes, and both short and long documents. When using `multiple_item_qa` mode, the `_multi` variants apply. For `long_text` configurations, Box splits the content into chunks using an embeddings model. You can configure the embeddings model and chunking strategy as part of the override. ### LLM parameter differences by provider The `llm_endpoint_params` options depend on the model provider: | Provider | Param type | Key difference | | - | - | - | | OpenAI | `openai_params` | Use `temperature` **or** `top_p`, not both | | Google | `google_params` | `temperature` works with `top_p` and `top_k` together | | AWS | `aws_params` | Same as Google: `temperature` works alongside `top_p` and `top_k` | For detailed override examples, see the AI model overrides guide and the override tutorial. ### Model versioning Box guarantees each AI agent configuration snapshot for at least 12 months, with a 6-month transition window when a new version is released. Default model changes are posted in the developer changelog. To avoid disruption, pin your agent configuration to a specific model version using overrides. For full details, see AI agent configuration versioning. ## Box AI for UI Elements The Box AI for UI Elements integration embeds question-answering directly into Content Preview within your application. This lets end users interact with Box AI without leaving your UI. ## User Activity Reports [User Activity Reports][uar] track Box AI interactions. Box admins can filter for the following action types: | Action type | Description | | - | - | | **AI query** | The user queried Box AI and received a response | | **Failed AI query** | The user queried Box AI but did not receive a response | [uar]: https://support.box.com/hc/en-us/articles/4415012490387-User-Activity-Report ## Guides in this section Step-by-step guides for each endpoint: ask, text generation, extraction, and model overrides. Override default models, prompts, and LLM parameters. Includes the default configuration reference and versioning policy. Full list of core and customer-enabled models with capability tiers, compliance badges, and API names. Get up and running in minutes with Python SDK walkthroughs for summarization and extraction. ## See Box AI in action These end-to-end tutorials show how to combine Box AI with other platform capabilities to build production-ready automations. Curate an enterprise repository into a Hub and ask questions with Box AI. Answers inherit Box permissions. Use Box AI Extract and metadata to automate accounts payable - extract vendors, totals, and dates from every invoice. Build an AI-powered knowledge base with Box Hubs and let sales reps query approved proposals in natural language. # Extract structured data with Box AI in Python Source: https://developer.box.com/guides/box-ai/quick-start/box-ai-extract Learn how to use Box AI to automatically extract structured data from documents and store it as searchable metadata using the Box Python SDK. Box AI exposes intelligent extraction capabilities that enable developers to automatically extract structured key-value pairs from documents through a single API call. This powerful feature transforms unstructured document content into actionable metadata without manual data entry, streamlining document processing workflows for invoices, forms, contracts, and other business documents. This quick start demonstrates how to configure the Box Python SDK, create a metadata template, and use Box AI to extract invoice data and store it as searchable metadata in Box. A free developer account gives you access to the Box AI API. Try document summarization, question answering, and metadata extraction through the API. The first step for any Box integration is to create and configure a Box application. 1. Go to Box Developer Console. 2. For this quick start, create an App with the `Client Credentials Grant` application type. 3. Once the app is created, enable the following scopes: * Read all files and folders stored in Box * Write all files and folders stored in Box * Manage AI For more information about creating a new Box application, see Create your first application. This step requires Admin access to your Box Enterprise. If you do not have access in your current environment, contact your Box administrator. Box AI enables you to extract data from documents in several ways: | Type | Description | Use case | | - | - | - | | Freeform extraction | Accepts a string prompt. | Provide a natural language prompt. | | Structured extraction with template | Accepts a Box Metadata template key. | Define fields and data types once; simplifies pushing back to Box as metadata. | | Structured extraction with fields | Accepts a JSON array of fields. | Run one-off extractions without creating a template. | | Enhanced Extract Agent | Uses a specialized agent with a reasoning model. | Use for complex documents or nuanced extraction; works with structured templates or fields. | For this quick start, create a Box metadata template to define the fields you want to extract from your documents. See Customizing Metadata Templates for a detailed walkthrough of the steps to create a Box metadata template in the Box Admin Console. 1. Give your template a name, for example, `Box AI extract quick start`. 2. Create the following fields: | Field name | Type | Description | | - | - | - | | Client Name | Text | The name of the client receiving the invoice | | Invoice Amount | Number | The total amount of the invoice after taxes and fees | | Products | Text | The names of the products delivered in the invoice, returned as a comma-delimited string | The field description is used by Box AI to supplement the prompt to the LLM to ensure the right data is extracted. 3. Click **Save** to create your template. Make note of the template key to use in a later step. When you save the template, a list of templates appears. To find the template key, open the template you just created and inspect the URL. The last part of the path is your template key. For example, the URL might look like this: `https://app.box.com/master/metadata/templates/boxAiExtractquick start`. In this case, the template key is `boxAiExtractquick start`. After preparing the template, select a file to test. For this quick start, use this sample invoice document. 1. Download the test document, and then drag and drop it into your Box account. 2. Get the file ID by opening the file in Box and inspecting the URL. The last part of the path is your file ID. For example, the URL might look like this: `https://app.box.com/file/2064123286902` In this case, the file ID is `2064123286902`. Now set up your development environment to run the extraction. For this quick start, use Python and the Box Python SDK version 10. Make sure you have Python 3.11 or higher installed on your machine. 1. Create a new directory for your project and navigate into it. 2. Create a virtual environment: ```bash theme={null} python3 -m venv .venv source .venv/bin/activate ``` 3. Install the Box Python SDK: ```bash theme={null} pip install "boxsdk~=10" ``` 4. Install the `python-dotenv` package to load environment variables from the `.env` file: ```bash theme={null} pip install python-dotenv ``` 5. Create an `.env` file in the root of your project directory and add the following environment variables, replacing the placeholder values with your actual Box app credentials and the IDs from the previous steps: ```bash theme={null} BOX_DEVELOPER_TOKEN=your_box_developer_token BOX_METADATA_TEMPLATE_KEY=your_metadata_template_key BOX_FILE_ID=your_box_file_id ``` To get your developer token, go to the Box Developer Console, open your app, and navigate to the **Configuration** tab. 6. Click **Generate Developer Token** to create a new token. For simplicity, this quick start uses a short-lived developer token. In production, you should authenticate using your app’s configured method (for example, Client Credentials Grant) instead of a developer token. Your development environment is now ready to create the Python script to extract data from the document using Box AI. 1. Create a new file named `extract.py` in the root of your project directory and add the following code: ```python theme={null} import os from dotenv import load_dotenv from box_sdk_gen import ( AiItemBase, BoxClient, BoxDeveloperTokenAuth, CreateAiExtractStructuredMetadataTemplate, CreateAiExtractStructuredMetadataTemplateTypeField, CreateFileMetadataByIdScope ) load_dotenv() developer_token = os.getenv("BOX_DEVELOPER_TOKEN") file_id = os.getenv("BOX_FILE_ID") template_key = os.getenv("BOX_METADATA_TEMPLATE_KEY") def get_box_client(token: str) -> BoxClient: if not developer_token: raise ValueError("BOX_DEVELOPER_TOKEN is not set in environment variables.") auth = BoxDeveloperTokenAuth(token=token) client = BoxClient(auth=auth) return client def main(): client = get_box_client(token=developer_token) me = client.users.get_user_me() print(f"My user ID is {me.id}") if __name__ == "__main__": main() ``` This code loads the environment variables from the `.env` file, initializes the Box SDK client, and prints the current user's ID to validate that the client is working correctly. 2. Run the script using the following command in your terminal: ```bash theme={null} python extract.py ``` If the Box SDK client is set up correctly, the console displays your user ID. For example: ```bash theme={null} My user ID is 123456789 ``` With a working Box SDK client, you can add the code to extract data from the document using Box AI. 1. Between the `get_box_client` function and the `main` function, add the following function: ```python theme={null} def extract_metadata(client: BoxClient, file_id: str, template_key: str) -> dict: metadata = client.ai.create_ai_extract_structured( [AiItemBase(id=file_id)], metadata_template=CreateAiExtractStructuredMetadataTemplate( template_key=template_key, type=CreateAiExtractStructuredMetadataTemplateTypeField.METADATA_TEMPLATE, scope="enterprise", ), ) return metadata.to_dict()['answer'] ``` This function uses the Box AI `create_ai_extract_structured` method to extract metadata from the specified file. Your BoxClient, the file ID, and the metadata template key created earlier are sent to the function, which returns the extracted metadata as a dictionary. 2. Add the function call to extract the metadata in the `main` function. Ensure that the new `main` function contains the following logic: ```python theme={null} def main(): client = get_box_client(token=developer_token) me = client.users.get_user_me() print(f"My user ID is {me.id}") metadata = extract_metadata(client=client, file_id=file_id, template_key=template_key) print(f"Extracted Metadata: {metadata}") ``` The SDK handles the API call to Box AI and returns the extracted metadata as an `AiExtractStructuredResponse` object. In this quick start, the code converts this object to a dictionary and returns the `answer` field that contains the extracted key/value pairs. 3. Print out the extracted metadata to the console to verify that the extraction was successful by running the following command in your terminal: ```bash theme={null} python extract.py ``` If the extraction was successful, the console displays your user ID followed by the extracted metadata from the invoice document. ```bash theme={null} My user ID is 123456789 Extracted Metadata: {'clientName': 'ACME Inc', 'invoiceAmount': 1106.06, 'products': 'Polyol, Diisocyanate, Carbon Dioxide, Laser, Lens, Oleic Acid, Glycerine, Sodium Tallowate, Paint Base, Polypropylene, Rubber, Additive, Pigment, Aluminum Silicate, Magnesium Silicate, Zinc Oxide, Distilled Solvent, Petroleum Distillate, Sulfur Dioxide, Sodium Benzoate, Dust cap, Ferrite cap, Cone and coil assembly, Cleaner, Polypropylene pellets, Polypropylene chips, Polypropylene blocks, Polypropylene slag, Parts Wash Solvent, Jar, Plastic Bottle - 15.2 FL Oz (450 ml), Polymer'} ``` Now that you have extracted metadata from the document, you can use these key/value pairs in your application: push to databases, integrate with CRMs, feed to agents for processing, or trigger automated workflows. This quick start demonstrates pushing the extracted data back to Box as file metadata. Box metadata management enables powerful filtering and search capabilities across your content. For example, you can query all invoices over \$500 from the last 30 days, create dashboards in Box Apps, or surface key document insights directly in the Box web application. 1. Push the extracted metadata back to Box by adding the following function between the `extract_metadata` function and the `main` function: ```python theme={null} def push_metadata(client: BoxClient, file_id: str, metadata: dict, template_key: str) -> dict: attached_metadata = client.file_metadata.create_file_metadata_by_id( file_id, CreateFileMetadataByIdScope.ENTERPRISE, template_key, metadata, ) return attached_metadata.to_dict() ``` This function uses the `create_file_metadata_by_id` method to attach metadata to the specified file, processing the BoxClient, file ID, metadata dictionary, and template key. The API itself returns a `MetadataFull` object. The function converts this object to a dictionary and returns it. 2. Add the function call to push the metadata in the `main` function. Ensure that the updated `main` function contains the following logic: ```python theme={null} def main(): client = get_box_client(token=developer_token) me = client.users.get_user_me() print(f"My user ID is {me.id}") metadata = extract_metadata(client=client, file_id=file_id, template_key=template_key) print(f"Extracted Metadata: {metadata}") attached_metadata = push_metadata(client=client, file_id=file_id, metadata=metadata, template_key=template_key) print(f"Attached Metadata: {attached_metadata}") ``` 3. Run the following command in your terminal: ```bash theme={null} python extract.py ``` If the script is successful, the console displays your user ID, the extracted metadata, and the attached metadata response from Box. For example: ```bash theme={null} My user ID is 123456789 Extracted Metadata: {'clientName': 'ACME Inc', 'invoiceAmount': 1106.06, 'products': 'Polyol, Diisocyanate, Carbon Dioxide, Laser, Lens, Oleic Acid, Glycerine, Sodium Tallowate, Paint Base, Polypropylene, Rubber, Additive, Pigment, Aluminum Silicate, Magnesium Silicate, Zinc Oxide, Distilled Solvent, Petroleum Distillate, Sulfur Dioxide, Sodium Benzoate, Dust cap, Ferrite cap, Cone and coil assembly, Cleaner, Polypropylene pellets, Polypropylene chips, Polypropylene blocks, Polypropylene slag, Parts Wash Solvent, Jar, Plastic Bottle - 15.2 FL Oz (450 ml), Polymer'} Attached Metadata: {'invoiceAmount': 1106.06, 'products': 'Polyol, Diisocyanate, Carbon Dioxide, Laser, Lens, Oleic Acid, Glycerine, Sodium Tallowate, Paint Base, Polypropylene, Rubber, Additive, Pigment, Aluminum Silicate, Magnesium Silicate, Zinc Oxide, Distilled Solvent, Petroleum Distillate, Sulfur Dioxide, Sodium Benzoate, Dust cap, Ferrite cap, Cone and coil assembly, Cleaner, Polypropylene pellets, Polypropylene chips, Polypropylene blocks, Polypropylene slag, Parts Wash Solvent, Jar, Plastic Bottle - 15.2 FL Oz (450 ml), Polymer', 'clientName': 'ACME Inc', '$parent': 'file_1956534287859', '$template': 'boxAiExtractquick start', '$scope': 'enterprise_899905961', '$version': 0, '$canEdit': True, '$id': '4d4f0b55-d45a-4ba4-9ff6-1241182cb76a', '$type': 'boxAiExtractquick start-c4024235-2384-49f4-9286-ada6d68fd6a9', '$typeVersion': 0, 'extra_data': {'invoiceAmount': 1106.06, 'products': 'Polyol, Diisocyanate, Carbon Dioxide, Laser, Lens, Oleic Acid, Glycerine, Sodium Tallowate, Paint Base, Polypropylene, Rubber, Additive, Pigment, Aluminum Silicate, Magnesium Silicate, Zinc Oxide, Distilled Solvent, Petroleum Distillate, Sulfur Dioxide, Sodium Benzoate, Dust cap, Ferrite cap, Cone and coil assembly, Cleaner, Polypropylene pellets, Polypropylene chips, Polypropylene blocks, Polypropylene slag, Parts Wash Solvent, Jar, Plastic Bottle - 15.2 FL Oz (450 ml), Polymer', 'clientName': 'ACME Inc'}} ``` ## Resources * Final code * Extract Structured Metadata API Reference * Create metadata instance on file * Box Python SDK # Structured extraction with the Box AI Enhanced Extract Agent in Python Source: https://developer.box.com/guides/box-ai/quick-start/box-ai-extract-enhanced Learn how to use the Box AI Enhanced Extract Agent to automatically extract data from large, complex documents using the Box Python SDK. Box AI exposes intelligent extraction capabilities that enable developers to automatically extract key-value pairs from documents through a single API call. The Enhanced Extract Agent uses advanced reasoning models to transform complex unstructured document content into actionable metadata without manual data entry, streamlining document processing workflows for invoices, forms, contracts, and other business documents. This quick start demonstrates how to configure the Box Python SDK and use Box AI to extract data from a stock purchase agreement stored in Box. A free developer account gives you access to the Box AI API. Try document summarization, question answering, and metadata extraction through the API. The first step for any Box integration is to create and configure a Box application. 1. Go to Box Developer Console. 2. For this quick start, create an App with the `Client Credentials Grant` application type. 3. Once the app is created, enable the following scopes: * Read all files and folders stored in Box * Write all files and folders stored in Box * Manage AI For more information about creating a new Box application, see Create your first application. After preparing the template, select a file to test. For this quick start, use this sample stock purchase agreement. 1. Download the test document, and then drag and drop it into your Box account. 2. Get the file ID by opening the file in Box and inspecting the URL. The last part of the path is your file ID. For example, the URL might look like this: `https://app.box.com/file/2064123286902` In this case, the file ID is `2064123286902`. Now set up your development environment to run the extraction. For this quick start, use Python and the Box Python SDK version 10. Make sure you have Python 3.11 or higher installed on your machine. 1. Create a new directory for your project and navigate into it. 2. Create a virtual environment: ```bash theme={null} python3 -m venv .venv source .venv/bin/activate ``` 3. Install the Box Python SDK: ```bash theme={null} pip install "boxsdk~=10" ``` 4. Install the `python-dotenv` package to load environment variables from the `.env` file: ```bash theme={null} pip install python-dotenv ``` 5. Create an `.env` file in the root of your project directory and add the following environment variables, replacing the placeholder values with your actual Box app credentials and the IDs from the previous steps: ```bash theme={null} BOX_DEVELOPER_TOKEN=your_box_developer_token BOX_METADATA_TEMPLATE_KEY=your_metadata_template_key BOX_FILE_ID=your_box_file_id ``` To get your developer token, go to the Box Developer Console, open your app, and navigate to the **Configuration** tab. 6. Click **Generate Developer Token** to create a new token. For simplicity, this quick start uses a short-lived developer token. In production, you should authenticate using your app’s configured method (for example, Client Credentials Grant) instead of a developer token. Your development environment is now ready to create the Python script to extract data from the document using Box AI. 1. Create a new file named `enhanced-extract.py` in the root of your project directory and add the following code: ```python theme={null} import os from dotenv import load_dotenv from box_sdk_gen import ( AiAgentReference, AiAgentReferenceTypeField, AiItemBase, BoxClient, BoxDeveloperTokenAuth, CreateAiExtractStructuredFields, CreateAiExtractStructuredFieldsOptionsField ) load_dotenv() developer_token = os.getenv("BOX_DEVELOPER_TOKEN") file_id = os.getenv("BOX_FILE_ID") def get_box_client(token: str) -> BoxClient: if not developer_token: raise ValueError("BOX_DEVELOPER_TOKEN is not set in environment variables.") auth = BoxDeveloperTokenAuth(token=token) client = BoxClient(auth=auth) return client def main(): client = get_box_client(token=developer_token) me = client.users.get_user_me() print(f"My user ID is {me.id}") if __name__ == "__main__": main() ``` This code loads the environment variables from the `.env` file, initializes the Box SDK client, and prints the current user's ID to validate that the client is working correctly. 2. Run the script using the following command in your terminal: ```bash theme={null} python enhanced-extract.py ``` If the Box SDK client is set up correctly, the console displays your user ID. For example: ```bash theme={null} My user ID is 123456789 ``` With a working Box SDK client, you can add the code to extract data from the document using Box AI. 1. Between the `get_box_client` function and the `main` function, add the following function: ```python theme={null} def extract_metadata(client: BoxClient, file_id: str) -> dict: enhanced_extract_config = AiAgentReference( id="enhanced_extract_agent", type=AiAgentReferenceTypeField.AI_AGENT_ID ) fields=[ CreateAiExtractStructuredFields( key="parties", display_name="Parties", description="The named parties involved", prompt="A comma separated list of the named parties involved", type="string", ), CreateAiExtractStructuredFields( key="effectiveDate", display_name="Effective date", description="The effective date of the contract", prompt="The effective date of the contract", type="date", ), CreateAiExtractStructuredFields( key="purchasePrice", display_name="Purchase price", description="The purchase price stated in the contract", prompt="The purchase price stated in the contract", type="float", ), CreateAiExtractStructuredFields( key="summary", display_name="Summary", description="A summary of the contract in 50 words or less", prompt="A summary of the contract in 50 words or less including key obligations", type="string", ), CreateAiExtractStructuredFields( key="recommendation", display_name="Recommendation", description="Should we make this purchase?", prompt="Given the financial details, would you recommend proceeding with the purchase? Answer Yes or No.", type="enum", options=[ CreateAiExtractStructuredFieldsOptionsField(key="Yes"), CreateAiExtractStructuredFieldsOptionsField(key="No"), ], ), ] metadata = client.ai.create_ai_extract_structured( [ AiItemBase(id=file_id) ], fields=fields, ai_agent=enhanced_extract_config, ) return metadata.to_dict()['answer'] ``` This function uses the Box AI `create_ai_extract_structured` method to extract metadata from the specified file. Your BoxClient and the file ID are sent to the function, which returns the extracted metadata as a dictionary. The `fields` parameter defines the specific data points to extract from the document. You can also reference a metadata template key instead to extract fields defined in a Box metadata template. 2. Add the function call to extract the metadata in the `main` function. Ensure that the new `main` function contains the following logic: ```python theme={null} def main(): client = get_box_client(token=developer_token) me = client.users.get_user_me() print(f"My user ID is {me.id}") metadata = extract_metadata(client=client, file_id=file_id) print(f"\n\nExtracted Metadata: {metadata}") ``` The SDK handles the API call to Box AI and returns the extracted metadata as an `AiExtractStructuredResponse` object. In this quick start, the code converts this object to a dictionary and returns the `answer` field that contains the extracted key/value pairs. 3. Print out the extracted metadata to the console to verify that the extraction was successful by running the following command in your terminal: ```bash theme={null} python enhanced-extract.py ``` If the extraction was successful, the console displays your user ID followed by the extracted metadata from the stock purchase agreement. ```bash theme={null} My user ID is 123456789 Extracted Metadata: {'parties': 'Argyle LLP, Suregood Family Trust', 'effectiveDate': '2023-03-31', 'purchasePrice': 231000000, 'summary': 'Argyle LLP agrees to purchase 51% of Erebor Life, Inc. from Suregood Family Trust for $231,000,000. The Seller must operate the business normally until closing, and the Buyer must pay the purchase price. The agreement is effective March 31, 2023.', 'recommendation': 'Yes'} ``` ## Resources * Final code * Extract Structured Metadata API Reference * Box Python SDK # Natural language extraction with Box AI in Python Source: https://developer.box.com/guides/box-ai/quick-start/box-ai-extract-freeform Learn how to use Box AI to automatically extract data from documents with natural language prompts using the Box Python SDK. Box AI exposes intelligent extraction capabilities that enable developers to automatically extract key-value pairs from documents through a single API call. This powerful feature transforms unstructured document content into actionable metadata without manual data entry, streamlining document processing workflows for invoices, forms, contracts, and other business documents. This quick start demonstrates how to configure the Box Python SDK and use Box AI to extract data from a W-2 stored in Box. A free developer account gives you access to the Box AI API. Try document summarization, question answering, and metadata extraction through the API. The first step for any Box integration is to create and configure a Box application. 1. Go to Box Developer Console. 2. For this quick start, create an App with the `Client Credentials Grant` application type. 3. Once the app is created, enable the following scopes: * Read all files and folders stored in Box * Write all files and folders stored in Box * Manage AI For more information about creating a new Box application, see Create your first application. After preparing the template, select a file to test. For this quick start, use this sample W-2. 1. Download the test document, and then drag and drop it into your Box account. 2. Get the file ID by opening the file in Box and inspecting the URL. The last part of the path is your file ID. For example, the URL might look like this: `https://app.box.com/file/2064123286902` In this case, the file ID is `2064123286902`. Now set up your development environment to run the extraction. For this quick start, use Python and the Box Python SDK version 10. Make sure you have Python 3.11 or higher installed on your machine. 1. Create a new directory for your project and navigate into it. 2. Create a virtual environment: ```bash theme={null} python3 -m venv .venv source .venv/bin/activate ``` 3. Install the Box Python SDK: ```bash theme={null} pip install "boxsdk~=10" ``` 4. Install the `python-dotenv` package to load environment variables from the `.env` file: ```bash theme={null} pip install python-dotenv ``` 5. Create an `.env` file in the root of your project directory and add the following environment variables, replacing the placeholder values with your actual Box app credentials and the IDs from the previous steps: ```bash theme={null} BOX_DEVELOPER_TOKEN=your_box_developer_token BOX_METADATA_TEMPLATE_KEY=your_metadata_template_key BOX_FILE_ID=your_box_file_id ``` To get your developer token, go to the Box Developer Console, open your app, and navigate to the **Configuration** tab. 6. Click **Generate Developer Token** to create a new token. For simplicity, this quick start uses a short-lived developer token. In production, you should authenticate using your app’s configured method (for example, Client Credentials Grant) instead of a developer token. Your development environment is now ready to create the Python script to extract data from the document using Box AI. 1. Create a new file named `freeform-extract.py` in the root of your project directory and add the following code: ```python theme={null} import os from dotenv import load_dotenv from box_sdk_gen import ( AiItemBase, BoxClient, BoxDeveloperTokenAuth ) # Set up environment variables load_dotenv() developer_token = os.getenv("BOX_DEVELOPER_TOKEN") file_id = os.getenv("BOX_FILE_ID") def get_box_client(token: str) -> BoxClient: if not developer_token: raise ValueError("BOX_DEVELOPER_TOKEN is not set in environment variables.") auth = BoxDeveloperTokenAuth(token=token) client = BoxClient(auth=auth) return client def main(): client = get_box_client(token=developer_token) # Get the current user and print the ID to validate the client is working me = client.users.get_user_me() print(f"My user ID is {me.id}") if __name__ == "__main__": main() ``` This code loads the environment variables from the `.env` file, initializes the Box SDK client, and prints the current user's ID to validate that the client is working correctly. 2. Run the script using the following command in your terminal: ```bash theme={null} python freeform-extract.py ``` If the Box SDK client is set up correctly, the console displays your user ID. For example: ```bash theme={null} My user ID is 123456789 ``` With a working Box SDK client, you can add the code to extract data from the document using Box AI. 1. Between the `get_box_client` function and the `main` function, add the following function: ```python theme={null} def extract_metadata(client: BoxClient, file_id: str) -> str: prompt = """ firstName, lastName, wages, federalTaxWithheld, socialSecurityWages, socialSecurityTaxWithheld, medicareWagesAndTips, medicareTaxWithheld, stateWages, stateTaxWithheld, localWagesAndTips, localTaxWithheld """ metadata = client.ai.create_ai_extract( prompt, [AiItemBase(id=file_id)] ) return metadata.to_dict()['answer'] ``` This function uses the Box AI `create_ai_extract` method to extract metadata from the specified file. Your BoxClient and the file ID are sent to the function, which returns the extracted metadata as a dictionary. 2. Add the function call to extract the metadata in the `main` function. Ensure that the new `main` function contains the following logic: ```python theme={null} def main(): client = get_box_client(token=developer_token) me = client.users.get_user_me() print(f"My user ID is {me.id}") metadata = extract_metadata(client=client, file_id=file_id) print(f"\n\nExtracted Metadata: {metadata}") ``` The SDK handles the API call to Box AI and returns the extracted metadata as an `AiExtractResponse` object. In this quick start, the code converts this object to a dictionary and returns the `answer` field that contains the extracted key/value pairs. 3. Print out the extracted metadata to the console to verify that the extraction was successful by running the following command in your terminal: ```bash theme={null} python freeform-extract.py ``` If the extraction was successful, the console displays your user ID followed by the extracted metadata from the W-2. ```bash theme={null} My user ID is 123456789 Extracted Metadata: {"firstName": "Wayne", "lastName": "Gatsby", "wages": "355000", "federalTaxWithheld": "125600", "socialSecurityWages": "355000", "socialSecurityTaxWithheld": "12300", "medicareWagesAndTips": "355000", "medicareTaxWithheld": "13200", "stateWages": "355,000", "stateTaxWithheld": "15000", "localWagesAndTips": "355000", "localTaxWithheld": "8000"} ``` ## Resources * Final code * Extract Freeform Metadata API Reference * Box Python SDK # Summarize files with Box AI in Python Source: https://developer.box.com/guides/box-ai/quick-start/box-ai-summarize Learn how to use Box AI to automatically extract structured data from documents and store it as searchable metadata using the Box Python SDK. Box AI exposes AI capabilities that enable developers to interrogate documents through a single API call. This powerful feature transforms unstructured document content into instant insights without the need to build and maintain complex Retrieval Augmented Generation (RAG) pipelines. This quick start demonstrates how to configure the Box Python SDK and use Box AI to summarize files in Box. A free developer account gives you access to the Box AI API. Try document summarization, question answering, and metadata extraction through the API. The first step for any Box integration is to create and configure a Box application. 1. Go to Box Developer Console. 2. For this quick start, create an App with the `Client Credentials Grant` application type. 3. Once the app is created, enable the following scopes: * Read all files and folders stored in Box * Manage AI For more information about creating a new Box application, see Create your first application. To use Box AI to get insights from documents, you need a file in Box. For this quick start, use this document containing federal code. 1. Download the test document, and then drag and drop it into your Box account. 2. Get the file ID by opening the file in Box and inspecting the URL. The last part of the path is your file ID. For example, the URL might look like this: `https://app.box.com/file/2064123286902` In this case, the file ID is `2064123286902`. Now set up your development environment to run this quickstart. This tutorial uses Python and the latest Box Python SDK to run the code. Make sure you have Python 3.11 or higher installed on your machine. 1. Create a new directory for your project and navigate into it. 2. Create a virtual environment: ```bash theme={null} python3 -m venv .venv source .venv/bin/activate ``` 3. Install the Box Python SDK: ```bash theme={null} pip install "boxsdk~=10" ``` 4. Install the `python-dotenv` package to load environment variables from the `.env` file: ```bash theme={null} pip install python-dotenv ``` 5. Create an `.env` file in the root of your project directory and add the following environment variables, replacing the placeholder values with your actual Box app credentials and the IDs from the previous steps: ```bash theme={null} BOX_DEVELOPER_TOKEN=your_box_developer_token BOX_FILE_ID=your_box_file_id ``` To get your developer token, go to the Box Developer Console, open your app, and navigate to the **Configuration** tab. 6. Click **Generate Developer Token** to create a new token. For simplicity, this quick start uses a short-lived developer token. In production, you should authenticate using your app’s configured method (for example, Client Credentials Grant) instead of a developer token. Your development environment is now ready to create the Python script to summarize the document using Box AI. 1. Create a new file named `summarize.py` in the root of your project directory and add the following code: ```python theme={null} import os from dotenv import load_dotenv from box_sdk_gen import ( AiItemAsk, AiItemAskTypeField, BoxClient, BoxDeveloperTokenAuth, CreateAiAskMode ) load_dotenv() developer_token = os.getenv("BOX_DEVELOPER_TOKEN") file_id = os.getenv("BOX_FILE_ID") def get_box_client(token: str) -> BoxClient: if not developer_token: raise ValueError("BOX_DEVELOPER_TOKEN is not set in environment variables.") auth = BoxDeveloperTokenAuth(token=token) client = BoxClient(auth=auth) return client def main(): client = get_box_client(token=developer_token) me = client.users.get_user_me() print(f"My user ID is {me.id}") if __name__ == "__main__": main() ``` This code loads the environment variables from the `.env` file, initializes the Box SDK client, and prints the current user's ID to validate that the client is working correctly. 2. Run the script using the following command in your terminal: ```bash theme={null} python summarize.py ``` If the Box SDK client is set up correctly, the console displays your user ID. For example: ```bash theme={null} My user ID is 123456789 ``` With a working Box SDK client, you can add the code to summarize the document using Box AI. 1. Between the `get_box_client` function and the `main` function, add the following function: ```python theme={null} def summarize_file(client: BoxClient, file_id: str) -> str: prompt = """ Summarize the content of the following file in a few sentences. """ summary = client.ai.create_ai_ask( CreateAiAskMode.SINGLE_ITEM_QA, prompt, [ AiItemAsk( id=file_id, type=AiItemAskTypeField.FILE ) ] ) return summary.to_dict()['answer'] ``` This function uses the Box AI `create_ai_ask` method to interrogate the specified file. Your BoxClient and file ID created earlier are sent to the function, which returns the summary of the file as a string. 2. Add the function call to summarize the file in the `main` function. Ensure that the new `main` function contains the following logic: ```python theme={null} def main(): client = get_box_client(token=developer_token) # Get the current user and print the ID to validate the client is working me = client.users.get_user_me() print(f"My user ID is {me.id}") summary = summarize_file(client=client, file_id=file_id) # Print the extracted metadata print(f"\n\nSummary: {summary}") ``` The SDK handles the API call to Box AI and returns the summary as an `AiResponseFull` object. In this quick start, the code converts this object to a dictionary and returns the `answer` field that contains the summary. 3. Print out the summary to the console to verify that the operation was successful by running the following command in your terminal: ```bash theme={null} python summarize.py ``` If the summarization was successful, the console displays your user ID followed by the summary from the test document. ```bash theme={null} My user ID is 123456789 Summary: **Summary of 28 CFR § 2.20 (Paroling Policy Guidelines)** - The document sets forth the U.S. Parole Commission’s purpose and framework for national parole policy, emphasizing guidelines to promote consistent, equitable parole decisions while preserving individualized review. - It provides a matrix of customary total time-to-release ranges by offense severity categories (1–8) and offender parole prognosis (salient factor scores), noting guidelines apply to inmates with good institutional adjustment and are discretionary. - A comprehensive Offense Behavior Severity Index (Chapters 1–12) assigns categories to specific crimes (homicide, assault, theft, drugs, firearms, national defense, etc.) with detailed rules, exceptions, and examples for grading offenses (including monetary thresholds and drug-quantity bands). - Chapter Thirteen contains general notes, multiple-offense scoring guidance, definitions of key terms, and the Salient Factor Scoring Manual explaining how to compute parole prognosis points (prior convictions, commitments, age, recent commitment-free period, probation/parole status, and older-offender adjustment). - The document also references reparole guidelines and authority to revise guidelines; aggravated crimes and Category Eight cases receive special treatment. ``` ## Resources * Final code * Box AI Ask API Reference * Box Python SDK # Box MCP server Source: https://developer.box.com/guides/box-mcp/index Hosted Model Context Protocol (MCP) server at https://mcp.box.com that lets AI agents search and use Box files over OAuth. The **Box MCP server** is a hosted [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) server at `https://mcp.box.com`. It lets AI agents and apps search and use Box files over OAuth, without you handling raw file payloads in the client. MCP is an open protocol that standardizes how applications provide context to LLMs. Agents call Box tools (search, Box AI, folders, and more) through this single endpoint. MCP ## How it works at a glance Follow this path when you are wiring or extending an integration: 1. **Box hosts MCP** at `https://mcp.box.com`. You connect your client or agent platform to this URL; you do not run Box’s MCP server yourself for the standard integration. 2. **An admin enables MCP** in the Box Admin Console and, for custom clients, creates **Integration Credentials** (OAuth client ID and client secret, redirect URI, and scopes such as **Content Actions**). 3. **Your application** passes the MCP endpoint, OAuth credentials, the MCP name `box-remote-mcp`, and an authorization token according to your platform’s MCP client requirements. | If you want to… | Go to | | - | - | | Understand identity, scopes, and that MCP does not bypass Box permissions | [Permission-aware agent access](/guides/box-mcp/permission-aware-access) | | Enable MCP in the Admin Console, configure OAuth, and connect your client | [Set up the MCP server](/guides/box-mcp/setup) | | Look up tool names and what each tool does | [Available tools](/guides/box-mcp/tools) | ## Platform setup guides Box MCP server supports an expanding range of AI platforms. Select a card below to view setup instructions for each one. Step-by-step setup for **end-user AI platforms** (for example ChatGPT, Claude, Copilot Studio, Figma, and others) lives in **Box product documentation** — see [Supported AI Platforms](https://docs.box.com/en/box-mcp/supported-ai-platforms) for the full partner list and platform guides. The **self-hosted** Box MCP server (open-source community project) is **deprecated**. Do not start new work on it. Existing references and troubleshooting remain in [Self-hosted Box MCP server (legacy)](/guides/box-mcp/self-hosted) under **Appendix**. Watch an interview with Box CTO, Ben Kus, and learn how MCP empowers AI agents to work dynamically across platforms, reducing the development effort. ``` ### Browser permissions The `allow` attribute enables clipboard operations and local network access for Google Chrome 142 and above and Microsoft Edge 143 and above. While designed for these browser versions, this attribute can be safely included for all browsers. Other browsers will ignore it. Without this attribute, embedded Box content might not work correctly with Box Tools, Device Trust, or the clipboard copy button. The Embed Widget Link Generation modal automatically includes this parameter in the generated code. ### Finding your shared link value The first step to building an embed `iframe` programmatically is to generate or find the value for the shared link. One way to find this value is by using the Box web app. Box Share Another way is to create a shared link with the API using the `PUT /files/:file_id` or `PUT /folders/:folder_id`. Then you can find this shared link value using the `GET /files/:id` or `GET /folders/:id` endpoint and passing in the query parameter `fields=shared_link`. ```curl theme={null} curl https://api.box.com/2.0/folders/12345?fields=shared_link \ -H "authorization: Bearer ACCESS_TOKEN" ``` ```json theme={null} "shared_link": { "url": "https://app.box.com/s/dsbJFzdO7qZxdfOHFzdO7qZxdfOH", "download_url": null, "vanity_url": null, ... } ``` You can also set the page to Root Folder/All Files page. Set the URL to `/folder/0` instead of the share link: `` ### Parameters Next, you will want to choose your view customization options. The following is a list of optional parameters you can configure. | | | | - | - | | `hideHubsGallery` | Hide or show navigation chevron button to go back to Hubs gallery. Can be `true` or `false` (default). | | `hideNavigationControls` | Hide or show navigation controls in Box Notes. | | `hidePreviewCloseButton` | Hide or show navigation controls in Box Preview. | | `showItemFeedActions` | Hide or show file comments or tasks. Can be `true` (default) or `false`. | | `showParentPath` | Hide or show the folder path in the header of the frame. Can be `true` (default) or `false`. | | `sortColumn` | The order the files or folders are sorted in. Can be `name`, `date` (default), or `size`. | | `sortDirection` | The sort direction of files or folders. Can be `ASC` (default) or `DESC`. | | `view` | The view type for your files or folders. Can be `list` (default) or `icon`. For logged-in users the view type from user preferences takes precedence. | | `uxLite` | Show the simplified content preview (Preview Light). Works only for files and Box Notes. | When you use `uxLite` with Box Notes, navigation controls are not displayed, regardless of the `hideNavigationControls` setting. All custom search parameters from the first-party app URL are passed to the embed widget modal and Content Preview. ### Full screen capabilities To enable full screen capabilities for the Box Embed snippet, include one or more of the following parameters if you want the object to be viewable in full screen within an ` ``` #### Using the close button When embedding the Box AI chat directly with `iframe` (without using the provided script), you can enable a close button within the chat interface that communicates with your parent application through `postMessage`. ##### Enabling the close button To display a close button (✕) in the corner of the iframe, add the `showCloseButton=true` query parameter to your `iframe` URL as follows: `https://app.box.com/ai-chat?hubId=YOUR_HUB_ID&showCloseButton=true` ##### How it works 1. When `showCloseButton=true` is set, an X button appears in the corner of the chat iframe. 2. When a user clicks this button, the iframe sends a `postMessage` event to the parent window. 3. The event contains `event.data.type` set to `"BOX_AI_CHAT_CLOSE"`. 4. Your hosting application listens for this event and handles the closing logic. ##### Implementation example ```javascript theme={null} window.addEventListener('message', (event) => { // Optional: validate origin is from Box for additional security // if (event.origin !== 'https://app.box.com') return; if (event.data && event.data.type === 'BOX_AI_CHAT_CLOSE') { closeChat(); } }); ``` ##### Event reference | Property | Value | Description | | - | - | - | | `event.data.type` | `"BOX_AI_CHAT_CLOSE"` | Indicates the user clicked the close button in the chat iframe. | ## Expiring embed links For files, another option is to call the `GET /files/:id` and request an `expiring_embed_link` using the `fields` query parameter. ```curl theme={null} curl https://api.box.com/2.0/files/12345?fields=expiring_embed_link \ -H "authorization: Bearer ACCESS_TOKEN" ``` ```json theme={null} { "etag": "1", "expiring_embed_link": { "token": { "access_token": "1!rFppcinUwwwDmB4G60nah7z...", "expires_in": 3646, "restricted_to": [ { "object": { "etag": "1", "file_version": { "id": "34567", "sha1": "1b8cda4e52cb7b58b354d8da0068908ecfa4bd00", "type": "file_version" }, "id": "12345", "name": "Image.png", "sequence_id": "1", "sha1": "1b8cda4e52cb7b58b354d8da0068908ecfa4bd00", "type": "file" }, "scope": "base_preview" }, ... ], "token_type": "bearer" }, "url": "https://cloud.app.box.com/preview/expiring_embed/...." }, "id": "12345", "type": "file" } ``` The `url` attribute can be used in an ` ``` ## Customized Previewer (UI Elements) To leverage advanced preview customization and event handling capabilities, use the Box UI Preview Element. To set up the Preview Element, start by installing the required components. The basic code will resemble the below when adding the JavaScript code to display a new previewer. ```js theme={null} var preview = new Box.Preview(); preview.show("FILE_ID", "ACCESS_TOKEN", { container: ".preview-container", showDownload: true }); ``` Replace the placeholders in the code sample with the following: * `FILE_ID`: The ID of the file uploaded to the application, which can be obtained from the object returned when uploading the file. * `ACCESS_TOKEN`: The primary Access Token set up when configuring the application or a downscoped version of the token. Due to the elevated privileges of the primary access token it's highly recommended that you use a downscoped version of the token in the Javascript code. See best practices for downscoping. # FAQ Source: https://developer.box.com/guides/embed/box-view/faq To get started with the New Box View, follow our guide here. Please follow our guide here to choose the best method for your use case. Supported [file types][file_types] can be found in our support article. * All documents supported on web preview are supported on mobile browsers (Safari for iOS and Chrome). * Full annotations support is available for mobile via the Content Preview UI Element, which leverages Box Annotations. * Mobile SDKs (for iOS and Android) do not support 360 Videos/Images, and 3D. * Mobile SDKs (for iOS and Android) do not support annotations (both read and write). Annotations are markup notes on a file rendering and allow developers to provide collaboration capabilities right from within the embedded Box preview in their application. Box Representations let you get the digital assets created for files stored in Box. You can use these endpoints to get PDF, text, image, and thumbnail representations for a file. Currently, Box View is only compatible with files that are stored in Box. You can delete the files from Box once you no longer need to display them. However, you would need to re-upload them again in order to generate the preview. For this reason, we recommend keeping the files stored in Box for at least as long as you want to be able to display them. To fix the CORS error, add each domain you wish to allow to make CORS requests via the application's configuration page. Wildcards are supported for the subdomain (`https://*.domain.com`). See the CORS guide for more information. See this guide for information on customizing the logo within a Preview UI Element. [file_types]: https://support.box.com/hc/en-us/articles/360043695794-Viewing-Different-File-Types-Supported-in-Box-Content-Preview # Box View Source: https://developer.box.com/guides/embed/box-view/index Box View is an embeddable service that allows developers to upload, convert, and display files in their web and mobile apps via a high-fidelity, interactive file viewer. ## Features ### View any file Embed documents, images, videos, 360-degree videos and images, 3D models, and dozens of other files in any web or mobile app using a standard ` ``` Always reach this page through the backend at `http://127.0.0.1:5000/review/` - never by double-clicking `review.html`. A double-clicked file opens over `file://`, where the relative `fetch("/api/...")` has no server to reach. The iframe `src` is then never set and you see only the header. The page and the API must share an origin. In production, the workbench app and the Box-facing backend typically live behind the same gateway (same origin), or you proxy `/api/*` to the backend, so these relative `fetch` calls resolve. You can exercise the whole pipeline from the command line before wiring it into a UI. Make sure you are in the `loan-review` directory and the virtual environment is activated before running any commands: ```bash theme={null} cd ~/loan-review source .venv/bin/activate ``` **1. Start the backend:** ```bash theme={null} python app.py ``` You should see `Running on http://127.0.0.1:5000`. Leave this terminal running. **2. Upload a document.** In a second terminal, upload a sample tax return or statement (any PDF works): ```bash theme={null} curl -X POST http://127.0.0.1:5000/api/documents \ -F "file=@sample-tax-return.pdf" ``` You should get back a file ID: ```json theme={null} { "file_id": "1234567890" } ``` **3. Fetch an embed URL** for that file ID: ```bash theme={null} curl http://127.0.0.1:5000/api/documents/1234567890/embed ``` ```json theme={null} { "embed_url": "https://app.box.com/preview/expiring_embed/gvoct6FE!YT_X1Lau..." } ``` **4. View it in the iframe.** With the backend still running, open the review page through the server, using your file ID as the last path segment: ``` http://127.0.0.1:5000/review/1234567890 ``` The document renders inside the iframe. If instead you see only the `Loan application 4815` header, you most likely opened `review.html` by double-clicking it (the `file://` protocol) - open it through the URL above instead. As a separate sanity check, pasting the embed URL from step 3 directly into the browser within a minute also renders the document, proving conversion succeeded. ## Troubleshooting 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. Check your `.env` file: * Verify `BOX_CLIENT_ID` and `BOX_CLIENT_SECRET` match the values in Developer Console > your app > Configuration. * Confirm `BOX_ENTERPRISE_ID` is your enterprise ID (Developer Console > account icon > Copy Enterprise ID). * Ensure the app type is Client Credentials Grant and the app has been authorized. The app has not been authorized by an admin yet. In the Developer Console, open the **Authorization** tab, click **Review and Submit**, and have your Box admin approve it in the Admin Console. See Platform App approval. Conversion may still be in progress, or the file type does not support preview. Wait a few seconds after upload and retry. Confirm the uploaded file is a supported type - see the supported file types. Flask does not have the `/review/` route registered. This almost always means you added the route but did not restart the server - Flask loads your code once at startup. Stop the server with `Ctrl+C` and run `python app.py` again. Also confirm `send_file` is imported (`from flask import Flask, request, jsonify, send_file`) and that the route exists in `app.py`. Running with `debug=True` enables auto-reload for future edits. You opened `review.html` directly (over the `file://` protocol) instead of through the backend. The page's relative `fetch("/api/documents/.../embed")` call only works when the page is served from the same origin as the API. Start the backend and open `http://127.0.0.1:5000/review/` instead of double-clicking the file. Open your browser's developer console to confirm - you will see the failed `fetch` request. The embed link has expired. Embed links last about one minute. Generate the link immediately before setting the iframe `src`, and request a new one each time the reviewer opens the document. This is almost always a **CORS allow-list** issue. The iframe review page loads a Box-hosted URL and never calls the Box API from your origin, so it works without CORS. Box Content Preview, however, calls `api.box.com` **directly from the browser** - in your developer console you will see repeated `GET https://api.box.com/2.0/files/?fields=...` requests returning `403`, thrown from `preview.js`. Fixes, in order: * Add the **exact** origin you open the page from to the CORS allow-list (Developer Console > your app > **Configuration** > **CORS Domains**), including scheme and port - for example `http://127.0.0.1:5000`. Remember that `127.0.0.1` and `localhost` are different origins; add whichever you use. Save and wait a minute or two, then restart and reload. * If it still `403`s, the downscoped token lacks permissions: confirm the **Read and write all files and folders** scope is enabled and that you **re-authorized** the app in the Admin Console afterward. * Confirm the file ID in the `/annotate/` URL matches the file the token was minted for - the token is downscoped to a single file, so a mismatched ID also returns `403`. ## Add in-context annotations with Box Content Preview The iframe embed is perfect for fast, read-only review. To let reviewers **mark up documents in context** - highlight a suspicious figure on a tax return, drop a point comment on a missing signature - render the same file with the Box Content Preview UI Element instead of the bare iframe. Content Preview powers preview in the Box web app itself and runs entirely client-side, so annotations stay inside your workbench. Content Preview needs a token in the browser. You already built the `/token` endpoint that returns a downscoped, file-scoped token with annotation scopes, so the front end never sees a full-privilege credential. **You must add your serving origin to the CORS allow-list before this step will work.** Box Content Preview makes API calls **directly from the browser to `api.box.com`**. Box rejects those calls with a `403 (Forbidden)` unless the page's origin is on your app's CORS allow-list (Developer Console > your app > **Configuration** > **CORS Domains**). Add the **exact** origin you open the page from, including the scheme and port - for example `http://127.0.0.1:5000`. Note that `http://127.0.0.1:5000` and `http://localhost:5000` are **different origins**: allow-list whichever you actually browse to (or add both). Changes can take a minute or two to take effect. Create a file called `annotate.html` in the project root, then add a route in `app.py` to serve it from the same origin as the API - just like the iframe page: ```python theme={null} @app.route("/annotate/") def annotate_page(file_id): return send_file("annotate.html") ``` ```html theme={null} Loan document review
``` Open it the same way you opened the iframe page - through the backend, at `http://127.0.0.1:5000/annotate/`, not by double-clicking the file. When `showAnnotations` is `true` and the token carries the annotation scopes, reviewers see annotation controls in the preview header. Annotations are stored against the file in Box, so a second reviewer opening the same document sees the first reviewer's notes (granted `annotation_view_all`). Box Annotations support **highlight comment**, **highlight only**, **draw**, and **point** annotation types. Point annotations work on both documents and images; highlight and draw annotations are document-only. Highlight annotations also require the `item_download` scope so the text layer is available. See the Annotations guide for the full matrix and for enabling real-time V4 annotations. If the preview still returns a `403` **after** you have allow-listed the origin, the downscoped token is missing permissions. Confirm the **Read and write all files and folders** scope is enabled on your app, and that you **re-authorized** the app in the Admin Console after enabling it - CCG scope changes do not take effect until the app is re-approved. See the dedicated scopes guide. ## Layer a richer review UI Once you are running Box Content Preview, you can progressively enrich the reviewer experience on the very same files - no re-upload, no second copy of the document. Use the Content Preview element's sidebar to show file details, versions, and an activity feed alongside the document, so reviewers can see the document's history and discuss it without leaving the page. Enable annotations in the activity feed and the document view together for a connected review workflow. See Content Sidebar. Box AI for UI Elements adds document Q\&A and summaries directly in the Content Preview header. A reviewer can ask "What is the borrower's reported annual income?" and get a cited answer grounded in the document. Pass `hasHeader: true` and `contentAnswersProps` to `preview.show()`. See Box AI for UI Elements. A loan application is rarely one file. Pass a `collection` of file IDs to `preview.show()` so reviewers can page through the tax return, W-2, and bank statements with navigation arrows inside one previewer. ```js theme={null} preview.show(fileId, access_token, { container: ".preview-container", collection: ["1234567890", "1234567891", "1234567892"], showAnnotations: true, }); ``` Mint the downscoped token with a resource and scopes that cover each file in the collection. Match the token's scopes to the reviewer's role. A junior reviewer might get `base_preview` + `annotation_view_self` + `annotation_edit` (sees only their own notes), while a senior underwriter gets `annotation_view_all`. After a decision is locked, drop the edit scopes to make the document effectively read-only. See Content Preview scopes. ## Scaling to production Instead of uploading everything to the Service Account root, create a folder per loan application and pass its ID as the upload parent. This keeps documents grouped, makes cleanup simple, and lets you apply retention policies at the folder level for compliance. Embed links expire in about a minute and downscoped tokens are short-lived by design. Generate both server-side at the moment they are needed. For Content Preview, pass a token generator function instead of a static string so the previewer can fetch a fresh token whenever it needs one. The client secret, enterprise ID, and full access token must never reach the browser. Store them in environment variables or a secrets manager, keep **Application Access** set to **App Access Only**, and restrict the CORS allow-list to the exact domains that run Box Content Preview. See Box View best practices. Use enterprise events to track previews, downloads, and annotation activity across every loan document. This gives compliance teams an audit trail of who reviewed what, and when - without instrumenting your own app. ## Next steps Automate accounts payable with Box AI Extract and metadata. Learn how upload, conversion, and embed fit together. # Turn supplier agreements into structured procurement data with Box AI Source: https://developer.box.com/tutorials/supplier-agreements Extract grouped vendor details and repeating delivery schedules from supplier agreements using the struct and table field types in Box AI structured extraction. Enterprise procurement documents rarely contain just a few flat fields. A single supplier agreement can include vendor contact details, delivery milestones, ship-to locations, and payment terms across paragraphs and tables. This tutorial shows you how to turn that complexity into clean, structured output. You use the `struct` and `table` field types in Box AI structured extraction to define a schema that matches the shape of the document, then call the extract endpoint with the [Enhanced Extract Agent](/guides/box-ai/ai-tutorials/extract-metadata-structured#enhanced-extract-agent) to return data that is ready for downstream systems, automations, and Box metadata. ## What you are building By the end of this tutorial, you have a working Python project that: * Authenticates to Box and reads a supplier agreement stored in a Box folder. * Defines an extraction schema that uses a `struct` field for grouped vendor details and a `table` field for a repeating delivery schedule. * Calls the [POST /2.0/ai/extract\_structured](/reference/post-ai-extract-structured) endpoint with the [Enhanced Extract Agent](/guides/box-ai/ai-tutorials/extract-metadata-structured#enhanced-extract-agent). * Maps the response into a procurement record that you can push to an ERP, procurement platform, or project tracker. ## Why struct and table The [POST /2.0/ai/extract\_structured](/reference/post-ai-extract-structured) endpoint supports two complex field types in addition to scalar types such as `string`, `float`, `date`, `enum`, and `multiSelect`: | Field type | Returns | Use it for | | - | - | - | | `struct` | A single nested JSON object | Related values that belong together, such as a vendor's legal name, address, and contact details. | | `table` | An array of JSON objects with the same schema | Repeating rows, such as delivery milestones, line items, or payment schedules. | These types let you mirror how the data is actually used. Instead of parsing one long vendor block or collapsing a delivery schedule into a single string after extraction, you map the result directly into a supplier master record or procurement workflow. For a full reference of both field types and their supported sub-field types, see struct and table field types. ## Prerequisites Before you start, make sure you have the following: * A free Box developer account, which gives you access to the Box AI API. * A Box application configured with **Client Credentials Grant** authentication. * Python 3.11 or higher. * A supplier agreement uploaded to Box. You can download a sample supplier agreement and upload it to a Box folder. Note its **file ID** from the URL. For example, if the URL is `https://app.box.com/file/123456789`, the file ID is `123456789`. * The following scopes enabled on your app: * Read and write all files and folders stored in Box * Manage AI ## Step-by-step process This tutorial uses one Box capability and one Box AI agent: | Component | Purpose | API | | - | - | - | | **Box AI Extract** | Pull grouped and repeating structured data from the agreement | `POST /2.0/ai/extract_structured` | | **Enhanced Extract Agent** | Improve accuracy for nested and repeating fields | `ai_agent.id = enhanced_extract_agent` | The Enhanced Extract Agent is a predefined Box AI agent, so you do not create or configure it yourself. You reference it by its ID (`enhanced_extract_agent`) in the `ai_agent` parameter of your extraction request, which you build in the **Build the extraction function** step below. To learn more, see the Enhanced Extract Agent reference. 1. Open your terminal and create a new project directory: ```bash theme={null} mkdir supplier-extraction && cd supplier-extraction ``` 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. Every time you open a new terminal window or tab, 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. 3. Install the required packages: ```bash theme={null} pip install boxsdk 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_ENTERPRISE_ID=your_enterprise_id AGREEMENT_FILE_ID=your_file_id ``` Never commit `.env` files to version control. Add `.env` to your `.gitignore`. Create a file called `box_client.py` and add the following code. It builds an authenticated client that you use to call Box AI through the SDK. ```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) ``` Client Credentials Grant is recommended for server-to-server automations where no end user is present. For other authentication options, see the authentication overview. A CCG application acts as a separate service account user that does not automatically have access to your content. Invite the service account email (found in the [Developer Console](https://app.box.com/developers/console) under **General Settings**) as a collaborator on the folder that contains your agreement. Without access, API calls return `404 Not found`. Create a file called `schema.py` and add the following code. The schema describes the agreement using two complex fields: * `vendor` is a `struct` field that groups related vendor details into one nested object. * `delivery_schedule` is a `table` field that returns one row per milestone. Each complex field requires a `fields` array that defines its sub-fields. Sub-fields support scalar types only. Nested `struct` or `table` types are not allowed. ```python theme={null} from box_sdk_gen import CreateAiExtractStructuredFields EXTRACTION_FIELDS = [ CreateAiExtractStructuredFields( key="agreement_effective_date", display_name="Effective date", type="date", prompt="The date the agreement takes effect.", ), CreateAiExtractStructuredFields( key="payment_terms", display_name="Payment terms", type="string", prompt="The payment terms, for example Net 30.", ), CreateAiExtractStructuredFields( key="vendor", display_name="Vendor", type="struct", prompt="The vendor or supplier issuing the agreement.", fields=[ CreateAiExtractStructuredFields(key="legal_name", type="string", display_name="Legal name"), CreateAiExtractStructuredFields(key="address", type="string", display_name="Address"), CreateAiExtractStructuredFields(key="country", type="string", display_name="Country"), CreateAiExtractStructuredFields(key="contact_person", type="string", display_name="Contact person"), CreateAiExtractStructuredFields(key="email", type="string", display_name="Email"), CreateAiExtractStructuredFields(key="phone", type="string", display_name="Phone"), ], ), CreateAiExtractStructuredFields( key="delivery_schedule", display_name="Delivery schedule", type="table", prompt="Each delivery milestone defined in the agreement.", fields=[ CreateAiExtractStructuredFields(key="phase", type="string", display_name="Phase"), CreateAiExtractStructuredFields(key="items", type="string", display_name="Items"), CreateAiExtractStructuredFields(key="delivery_date", type="date", display_name="Delivery date"), CreateAiExtractStructuredFields(key="destination", type="string", display_name="Destination"), CreateAiExtractStructuredFields(key="status", type="string", display_name="Status"), ], ), ] ``` Table extraction is not limited to visually formatted tables. The `table` type extracts repeating data whether it appears as a grid, key-value pairs, a form layout, or plain prose. Create a file called `extract.py` and add the following code. It uses the SDK's `create_ai_extract_structured` method to send the schema to Box AI and specifies the Enhanced Extract Agent, which improves accuracy for nested and repeating fields. ```python theme={null} from box_sdk_gen import AiItemBase, AiAgentReference, AiAgentReferenceTypeField from box_client import get_box_client from schema import EXTRACTION_FIELDS def extract_agreement(file_id: str) -> dict: client = get_box_client() response = client.ai.create_ai_extract_structured( items=[AiItemBase(id=file_id)], fields=EXTRACTION_FIELDS, ai_agent=AiAgentReference( id="enhanced_extract_agent", type=AiAgentReferenceTypeField.AI_AGENT_ID, ), ) return response.to_dict()["answer"] ``` The Enhanced Extract Agent is not strictly required, but it improves results for richer schemas and complex document layouts, especially when nested and repeating fields are involved. Create a file called `app.py` and add the following code. Box AI returns the `vendor` field as a nested object and the `delivery_schedule` field as a list of rows. This script runs the extraction and maps the result into a flat record that is ready for a downstream system. ```python theme={null} import json import os from dotenv import load_dotenv from extract import extract_agreement load_dotenv() def to_procurement_record(answer: dict) -> dict: vendor = answer.get("vendor", {}) schedule = answer.get("delivery_schedule", []) return { "vendor_name": vendor.get("legal_name"), "vendor_country": vendor.get("country"), "vendor_contact": vendor.get("contact_person"), "vendor_email": vendor.get("email"), "payment_terms": answer.get("payment_terms"), "effective_date": answer.get("agreement_effective_date"), "milestones": [ { "phase": row.get("phase"), "due": row.get("delivery_date"), "destination": row.get("destination"), "status": row.get("status"), } for row in schedule ], } if __name__ == "__main__": file_id = os.getenv("AGREEMENT_FILE_ID") answer = extract_agreement(file_id) print("Raw extraction:") print(json.dumps(answer, indent=2)) record = to_procurement_record(answer) print("\nProcurement record:") print(json.dumps(record, indent=2)) ``` At this point, your project directory should contain the following files: ``` supplier-extraction/ ├── .env ├── .venv/ ├── app.py ├── box_client.py ├── extract.py └── schema.py ``` Make sure you are in the `supplier-extraction` directory with the virtual environment activated, then run the script: ```bash theme={null} python3 app.py ``` Box AI returns the `struct` field as a single nested object and the `table` field as a list of objects. Your output looks similar to this: ```json theme={null} { "agreement_effective_date": "2026-06-01", "payment_terms": "Net 45", "vendor": { "legal_name": "Nexus Industrial Solutions Ltd.", "address": "47 Canary Wharf, Level 12, London, E14 5AB", "country": "United Kingdom", "contact_person": "Oliver Hartmann", "email": "o.hartmann@nexusindustrial.co.uk", "phone": "+44 20 7946 0832" }, "delivery_schedule": [ { "phase": "Phase 1 - Infrastructure", "items": "Server Racks (qty 6), UPS (qty 3)", "delivery_date": "2026-07-15", "destination": "Chicago, IL", "status": "Scheduled" }, { "phase": "Phase 2 - Networking", "items": "48-Port Switches (all), Fibre Cables (all)", "delivery_date": "2026-08-01", "destination": "Chicago, IL", "status": "Scheduled" }, { "phase": "Phase 3 - Security", "items": "NGFW Cluster (all), Remaining Racks", "delivery_date": "2026-08-20", "destination": "San Francisco, CA", "status": "Pending" }, { "phase": "Phase 4 - Services", "items": "Installation, Training", "delivery_date": "2026-09-01", "destination": "San Francisco, CA", "status": "Pending" } ] } ``` The script then prints the mapped procurement record, which you can send to your ERP, procurement platform, or project tracker. ## Troubleshooting Your virtual environment is not activated. Run `source .venv/bin/activate` from the project directory before running any `python3` commands. Each new terminal tab needs its own activation. Check your `.env` file: * Verify `BOX_CLIENT_ID` and `BOX_CLIENT_SECRET` match the values in Developer Console > Configuration. * Confirm `BOX_ENTERPRISE_ID` is your enterprise ID. * Ensure your app is authorized in the Developer Console and uses Client Credentials Grant. The service account does not have access to the file. Invite the service account email (found in Developer Console > General Settings) as a collaborator on the folder that contains your agreement. Nested `struct` and `table` types are not supported as sub-fields, and a `struct` or `table` field must include a `fields` array. Confirm your sub-fields use only scalar types, and add a `prompt` to the complex field to clarify what to extract. ## Scaling to production To make agreements searchable and routable inside Box, write the flattened top-level values back to the file as a metadata instance, then use metadata queries to filter by vendor, country, or effective date. See Automate invoice intake with Box AI Extract for an end-to-end metadata write-back pattern. Instead of running the script manually, register a webhook on your agreements folder so each upload triggers extraction. See Verify webhook signatures to validate incoming requests in production. Add a field-level `prompt` to guide extraction, and keep the Enhanced Extract Agent for multi-page agreements with dense tables. For consistent schemas across many documents, define the fields in a metadata template instead of inline. ## Next steps Watch a folder for new invoices, extract fields, and write them back as searchable metadata. See the full API specification for structured extraction. # Archives resources Source: https://developer.box.com/reference/v2025.0/archives-resources A set of endpoints used to manage archives. ## Archive An archive is a folder dedicated to storing content that is redundant, outdated, or trivial. Content in an archive is not accessible to its owner and collaborators. To use this feature, you must request GCM scope for your Box application. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier that represents an archive. | | `type` | `enum` | Yes | The value is always `archive`. Available options: `archive`. | | `name` | `string` | Yes | The name of the archive. The following restrictions to the archive name apply: names containing non-printable ASCII characters, forward and backward slashes (`/`, `\`), names with trailing spaces, and names `.` and `..` are not allowed. | | `size` | `integer` | Yes | The size of the archive in bytes. | | `description` | `string` | No | The description of the archive. Can be `null`. | | `owned_by` | `object` | No | The part of an archive API response that describes the user who owns the archive. | ```json Example theme={null} { "id": "12345", "type": "archive", "name": "Archive", "size": 123456789, "description": "This is an archive for important documents.", "owned_by": { "id": "12345", "type": "user" } } ``` ## Archives A list of archives. | Property | Type | Required | Description | | - | - | - | - | | `entries` | array of [Archive](#archive) | No | A list in which each entry represents an archive object. | | `limit` | `integer` | No | The limit that was used for these entries. This will be the same as the `limit` query parameter unless that value exceeded the maximum value allowed. The maximum value varies by API. | | `next_marker` | `string` | No | The marker for the start of the next page of results. Can be `null`. | ```json Example theme={null} { "entries": [ { "id": "12345", "type": "archive", "name": "Archive", "size": 123456789, "description": "This is an archive for important documents.", "owned_by": { "id": "12345", "type": "user" } } ], "limit": 1000, "next_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVii" } ``` # Box Doc Gen resources Source: https://developer.box.com/reference/v2025.0/docgen-resources Doc Gen is used to automatically generate documents. ## Box Doc Gen batch (Base) The basic representation of a Box Doc Gen batch object. A Box Doc Gen batch contains one or more Box Doc Gen jobs. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier that represents a Box Doc Gen batch. | | `type` | `enum` | Yes | The value will always be `docgen_batch`. Available options: `docgen_batch`. | ```json Example theme={null} { "id": "12345", "type": "docgen_batch" } ``` ## Box Doc Gen job A standard representation of a Box Doc Gen job. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier that represent a Box Doc Gen job. | | `type` | `enum` | Yes | The value will always be `docgen_job`. Available options: `docgen_job`. | | `batch` | [Box Doc Gen batch (Base)](#box-doc-gen-batch-base) | Yes | Box Doc Gen batch that the job belongs to. | | `template_file` | `File reference` | Yes | Box Doc Gen template used in the job. | | `template_file_version` | `File version (Base)` | Yes | File version of a template. | | `output_file` | `File reference` | No | The output file of the job. This property is `null` until the job is completed. Can be `null`. | | `output_file_version` | `File version (Base)` | No | File version of the output file. This property is `null` until the job is completed. Can be `null`. | | `status` | `enum` | Yes | Status of the job. Available options: `submitted`, `completed`, `failed`, `completed_with_error`, `pending`. | | `output_type` | `string` | Yes | Type of the generated file. | | `failures` | `object` | No | Errors and warnings that occurred during document generation. Can be `null`. | ```json Example theme={null} { "id": "12345", "type": "docgen_job", "batch": { "id": "12345", "type": "docgen_batch" }, "template_file": { "type": "file", "id": "42037322" }, "template_file_version": { "id": "12345", "type": "file_version" }, "output_file": { "type": "file", "id": "42037322" }, "output_file_version": { "id": "12345", "type": "file_version" }, "status": "completed", "output_type": "docx", "failures": { "errors": [ "The tag 'customer_name' was not provided in the input data." ], "warnings": [ "The tag 'optional_field' was provided but not used in the template." ] } } ``` ## Box Doc Gen job (Base) The basic representation of a Box Doc Gen job. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier that represent a Box Doc Gen job. | | `type` | `enum` | Yes | The value will always be `docgen_job`. Available options: `docgen_job`. | ```json Example theme={null} { "id": "12345", "type": "docgen_job" } ``` ## Box Doc Gen job (Full) A full representation of a Box Doc Gen job. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier that represent a Box Doc Gen job. | | `type` | `enum` | Yes | The value will always be `docgen_job`. Available options: `docgen_job`. | | `batch` | [Box Doc Gen batch (Base)](#box-doc-gen-batch-base) | Yes | Box Doc Gen batch that the job belongs to. | | `template_file` | `File reference` | Yes | Box Doc Gen template used in the job. | | `template_file_version` | `File version (Base)` | Yes | File version of a template. | | `output_file` | `File reference` | No | The output file of the job. This property is `null` until the job is completed. Can be `null`. | | `output_file_version` | `File version (Base)` | No | File version of the output file. This property is `null` until the job is completed. Can be `null`. | | `status` | `enum` | Yes | Status of the job. Available options: `submitted`, `completed`, `failed`, `completed_with_error`, `pending`. | | `output_type` | `string` | Yes | Type of the generated file. | | `failures` | `object` | No | Errors and warnings that occurred during document generation. Can be `null`. | | `created_at` | `string` | No | Time of job creation. Read-only. | | `created_by` | `User (Base)` | Yes | User who created the job. | | `enterprise` | `Enterprise reference` | Yes | ID of the enterprise. | | `source` | `string` | Yes | Source of the request. Read-only. | ```json Example theme={null} { "id": "12345", "type": "docgen_job", "batch": { "id": "12345", "type": "docgen_batch" }, "template_file": { "type": "file", "id": "42037322" }, "template_file_version": { "id": "12345", "type": "file_version" }, "output_file": { "type": "file", "id": "42037322" }, "output_file_version": { "id": "12345", "type": "file_version" }, "status": "completed", "output_type": "docx", "failures": { "errors": [ "The tag 'customer_name' was not provided in the input data." ], "warnings": [ "The tag 'optional_field' was provided but not used in the template." ] }, "created_at": "2022-05-11T10:56:11-07:00", "created_by": { "id": "11446498", "type": "user" }, "enterprise": { "id": "1910967", "type": "enterprise" }, "source": "api" } ``` ## Box Doc Gen jobs A list of Box Doc Gen jobs with a standard set of parameters. | Property | Type | Required | Description | | - | - | - | - | | `limit` | `integer` | No | The limit that was used for these entries. This will be the same as the `limit` query parameter unless that value exceeded the maximum value allowed. The maximum value varies by API. | | `next_marker` | `string` | No | The marker for the start of the next page of results. Can be `null`. | | `prev_marker` | `string` | No | The marker for the start of the previous page of results. Can be `null`. | | `entries` | array of [Box Doc Gen job](#box-doc-gen-job) | No | List of jobs. | ```json Example theme={null} { "limit": 1000, "next_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVii", "prev_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVih", "entries": [ { "id": "12345", "type": "docgen_job", "batch": { "id": "12345", "type": "docgen_batch" }, "template_file": { "type": "file", "id": "42037322" }, "template_file_version": { "id": "12345", "type": "file_version" }, "output_file": { "type": "file", "id": "42037322" }, "output_file_version": { "id": "12345", "type": "file_version" }, "status": "completed", "output_type": "docx", "failures": { "errors": [ "The tag 'customer_name' was not provided in the input data." ], "warnings": [ "The tag 'optional_field' was provided but not used in the template." ] } } ] } ``` ## Box Doc Gen jobs (Full) A list of Box Doc Gen jobs with a full set of parameters. | Property | Type | Required | Description | | - | - | - | - | | `limit` | `integer` | No | The limit that was used for these entries. This will be the same as the `limit` query parameter unless that value exceeded the maximum value allowed. The maximum value varies by API. | | `next_marker` | `string` | No | The marker for the start of the next page of results. Can be `null`. | | `prev_marker` | `string` | No | The marker for the start of the previous page of results. Can be `null`. | | `entries` | array of [Box Doc Gen job (Full)](#box-doc-gen-job-full) | No | List of jobs. | ```json Example theme={null} { "limit": 1000, "next_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVii", "prev_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVih", "entries": [ { "id": "12345", "type": "docgen_job", "batch": { "id": "12345", "type": "docgen_batch" }, "template_file": { "type": "file", "id": "42037322" }, "template_file_version": { "id": "12345", "type": "file_version" }, "output_file": { "type": "file", "id": "42037322" }, "output_file_version": { "id": "12345", "type": "file_version" }, "status": "completed", "output_type": "docx", "failures": { "errors": [ "The tag 'customer_name' was not provided in the input data." ], "warnings": [ "The tag 'optional_field' was provided but not used in the template." ] }, "created_at": "2022-05-11T10:56:11-07:00", "created_by": { "id": "11446498", "type": "user" }, "enterprise": { "id": "1910967", "type": "enterprise" }, "source": "api" } ] } ``` # Box Doc Gen templates resources Source: https://developer.box.com/reference/v2025.0/docgen-template-resources Doc Gen templates are used as input to generate documents. ## Box Doc Gen tags A list of Box Doc Gen tags. | Property | Type | Required | Description | | - | - | - | - | | `limit` | `integer` | No | The limit that was used for these entries. This will be the same as the `limit` query parameter unless that value exceeded the maximum value allowed. The maximum value varies by API. | | `next_marker` | `string` | No | The marker for the start of the next page of results. Can be `null`. | | `prev_marker` | `string` | No | The marker for the start of the previous page of results. Can be `null`. | | `entries` | array of [Box Doc Gen template tag](#box-doc-gen-template-tag) | No | List of tags. | ```json Example theme={null} { "limit": 1000, "next_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVii", "prev_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVih", "entries": [ { "tag_content": "{{item.name}}", "tag_type": "text", "json_paths": [ "products", "products.name" ] } ] } ``` ## Box Doc Gen tags processing message A message informing the user that document tags are still being processed. | Property | Type | Required | Description | | - | - | - | - | | `message` | `string` | Yes | A message informing the user that document tags are still being processed. Read-only. | ```json Example theme={null} { "message": "Processing tags for this file." } ``` ## Box Doc Gen template A Box Doc Gen template object. | Property | Type | Required | Description | | - | - | - | - | | `file` | `File reference` | No | Enable the file to generate a document from. | | `file_name` | `string` | No | The name of the template. Can be `null`. | ```json Example theme={null} { "file": { "type": "file", "id": "42037322" }, "file_name": "Official contract" } ``` ## Box Doc Gen template tag A Box Doc Gen template tag object. | Property | Type | Required | Description | | - | - | - | - | | `tag_content` | `string` | Yes | The content of the tag. Read-only. | | `tag_type` | `enum` | Yes | Type of the tag. Available options: `text`, `arithmetic`, `conditional`, `for-loop`, `table-loop`, `image`. Read-only. | | `json_paths` | array of `string` | Yes | List of the paths. Read-only. | ```json Example theme={null} { "tag_content": "{{item.name}}", "tag_type": "text", "json_paths": [ "products", "products.name" ] } ``` ## Box Doc Gen templates List of Box Doc Gen templates. | Property | Type | Required | Description | | - | - | - | - | | `limit` | `integer` | No | The limit that was used for these entries. This will be the same as the `limit` query parameter unless that value exceeded the maximum value allowed. The maximum value varies by API. | | `next_marker` | `string` | No | The marker for the start of the next page of results. Can be `null`. | | `prev_marker` | `string` | No | The marker for the start of the previous page of results. Can be `null`. | | `entries` | array of [Box Doc Gen template](#box-doc-gen-template) | No | A list of templates. | ```json Example theme={null} { "limit": 1000, "next_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVii", "prev_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVih", "entries": [ { "file": { "type": "file", "id": "42037322" }, "file_name": "Official contract" } ] } ``` # Enterprise Configurations resources Source: https://developer.box.com/reference/v2025.0/enterprise-configurations-resources Enterprise configurations admins interact with to drive Box product behavior. ## Enterprise configuration The enterprise configuration for a given enterprise. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | No | The identifier of the enterprise configuration which is the ID of the enterprise. | | `type` | `enum` | No | The value will always be `enterprise_configuration`. Available options: `enterprise_configuration`. | | `security` | [Enterprise configuration security](#enterprise-configuration-security) | No | The enterprise configurations for the security category. Can be `null`. | | `content_and_sharing` | [Enterprise configuration content and sharing](#enterprise-configuration-content-and-sharing) | No | The enterprise configurations for the content and sharing category. Can be `null`. | | `user_settings` | [Enterprise configuration user settings](#enterprise-configuration-user-settings) | No | The enterprise configurations for the user settings category. Can be `null`. | | `shield` | [Enterprise configuration shield](#enterprise-configuration-shield) | No | The enterprise configurations for the shield category. If shield is turned off for given enterprise, the response will be 404. Can be `null`. | ```json Example theme={null} { "id": "11446498", "type": "enterprise_configuration", "security": { "is_managed_user_signup_enabled": { "is_used": true, "value": true }, "is_managed_user_signup_notification_enabled": { "is_used": true, "value": true }, "is_managed_user_signup_corporate_email_enabled": { "is_used": true, "value": true }, "is_new_user_notification_daily_digest_enabled": { "is_used": true, "value": true }, "is_managed_user_email_change_disabled": { "is_used": true, "value": true }, "is_multi_factor_auth_required": { "is_used": true, "value": true }, "is_weak_password_prevention_enabled": { "is_used": true, "value": true }, "is_password_leak_detection_enabled": { "is_used": true, "value": true }, "last_password_reset_at": { "is_used": true, "value": "2012-12-12T10:53:43-08:00" }, "is_password_request_notification_enabled": { "is_used": true, "value": true }, "is_password_change_notification_enabled": { "is_used": true, "value": true }, "is_strong_password_for_ext_collab_enabled": { "is_used": true, "value": true }, "is_managed_user_migration_disabled": { "is_used": true, "value": true }, "join_link": { "is_used": true, "value": "value of configuration" }, "join_url": { "is_used": true, "value": "value of configuration" }, "failed_login_attempts_to_trigger_admin_notification": { "is_used": true, "value": 1234 }, "password_min_length": { "is_used": true, "value": 1234 }, "password_min_uppercase_characters": { "is_used": true, "value": 1234 }, "password_min_numeric_characters": { "is_used": true, "value": 1234 }, "password_min_special_characters": { "is_used": true, "value": 1234 }, "password_reset_frequency": { "is_used": true, "value": "value of configuration" }, "previous_password_reuse_limit": { "is_used": true, "value": "value of configuration" }, "session_duration": { "is_used": true, "value": "value of configuration" }, "external_collab_multi_factor_auth_settings": { "is_used": true, "value": { "denylist_domains": [ "example.com" ], "denylist_emails": [ "spam@example.com" ], "allowlist_domains": [ "example.com" ], "allowlist_emails": [ "mail@example.com" ], "state": "enabled", "scheduled_status": "inprogress", "scheduled_at": "2012-12-12T10:53:43-08:00", "factor_type_settings": "totp" } }, "keysafe": { "is_used": true, "value": { "keysafe_enabled": true, "cloud_provider": "", "key_id": "1234", "account_id": "1234", "location_id": "1234", "project_id": "1234", "keyring_id": "1234" } }, "is_custom_session_duration_enabled": { "is_used": true, "value": true }, "custom_session_duration_value": { "is_used": true, "value": "value of configuration" }, "custom_session_duration_groups": { "is_used": true, "value": [ { "id": "1234", "name": "Group Name" } ] }, "multi_factor_auth_type": { "is_used": true, "value": "value of configuration" }, "enforced_mfa_frequency": { "is_used": true, "value": { "days": 30, "hours": 24 } } }, "content_and_sharing": { "enterprise_feature_settings": [ { "is_used": true, "value": { "id": "eyJlbnRlcnByaXNlSWQiOiIxMTExNDQyNzY3IiwiZmVhdHVyZUlkIjoiY2FudmFzIn0=", "feature": { "id": "canvas" }, "state": "enabled", "can_configure": true, "is_configured": true, "allowlist": [ { "type": "user", "id": "12345" } ], "denylist": [ { "type": "user", "id": "12345" } ] } } ], "sharing_item_type": { "is_used": true, "value": "value of configuration" }, "shared_link_company_definition": { "is_used": true, "value": "value of configuration" }, "shared_link_access": { "is_used": true, "value": "value of configuration" }, "shared_link_default_access": { "is_used": true, "value": "value of configuration" }, "shared_link_default_permissions_selected": { "is_used": true, "value": { "shared_links_option": "settings_shared_link_preview", "default_shared_link_type": "settings_default_shared_link_preview", "notes_shared_link_option": "settings_notes_option_to_edit", "default_notes_shared_link_type": "settings_default_notes_shared_link_to_edit" } }, "is_open_custom_urls_disabled": { "is_used": true, "value": true }, "is_custom_domain_hidden_in_shared_link": { "is_used": true, "value": true }, "collaboration_permissions": { "is_used": true, "value": { "is_co_owner_role_enabled": true, "is_editor_role_enabled": true, "is_previewer_role_enabled": true, "is_previewer_uploader_role_enabled": true, "is_uploader_role_enabled": true, "is_viewer_role_enabled": true, "is_viewer_uploader_role_enabled": true } }, "default_collaboration_role": { "is_used": true, "value": "value of configuration" }, "is_invite_privilege_restricted": { "is_used": true, "value": true }, "collaboration_restrictions": { "is_used": true, "value": [ "internal" ] }, "is_collaborator_invite_links_disabled": { "is_used": true, "value": true }, "is_invite_group_collaborator_disabled": { "is_used": true, "value": true }, "is_ownership_transfer_restricted": { "is_used": true, "value": true }, "external_collaboration_status": { "is_used": true, "value": "enable_external_collaboration" }, "external_collaboration_allowlist_users": { "is_used": true, "value": [ { "id": 1234, "name": "John Doe", "email": "johndoe@box.com" } ] }, "is_watermarking_enterprise_feature_enabled": { "is_used": true, "value": true }, "is_root_content_creation_restricted": { "is_used": true, "value": true }, "is_tag_creation_restricted": { "is_used": true, "value": true }, "tag_creation_restriction": { "is_used": true, "value": "value of configuration" }, "is_email_uploads_enabled": { "is_used": true, "value": true }, "is_custom_settings_enabled": { "is_used": true, "value": true }, "is_forms_login_required": { "is_used": true, "value": true }, "is_forms_branding_default_enabled": { "is_used": true, "value": true }, "is_cc_free_trial_active": { "is_used": true, "value": true }, "is_file_request_editors_allowed": { "is_used": true, "value": true }, "is_file_request_branding_default_enabled": { "is_used": true, "value": true }, "is_file_request_login_required": { "is_used": true, "value": true }, "is_shared_links_expiration_enabled": { "is_used": true, "value": true }, "shared_links_expiration_days": { "is_used": true, "value": 1234 }, "is_public_shared_links_expiration_enabled": { "is_used": true, "value": true }, "public_shared_links_expiration_days": { "is_used": true, "value": 1234 }, "shared_expiration_target": { "is_used": true, "value": "value of configuration" }, "is_shared_links_expiration_notification_enabled": { "is_used": true, "value": true }, "shared_links_expiration_notification_days": { "is_used": true, "value": 1234 }, "is_shared_links_expiration_notification_prevented": { "is_used": true, "value": true }, "is_auto_delete_enabled": { "is_used": true, "value": true }, "auto_delete_days": { "is_used": true, "value": 1234 }, "is_auto_delete_expiration_modification_prevented": { "is_used": true, "value": true }, "auto_delete_target": { "is_used": true, "value": "value of configuration" }, "is_collaboration_expiration_enabled": { "is_used": true, "value": true }, "collaboration_expiration_days": { "is_used": true, "value": 1234 }, "is_collaboration_expiration_modification_prevented": { "is_used": true, "value": true }, "is_collaboration_expiration_notification_enabled": { "is_used": true, "value": true }, "collaboration_expiration_target": { "is_used": true, "value": "value of configuration" }, "trash_auto_clear_time": { "is_used": true, "value": 1234 }, "permanent_deletion_access": { "is_used": true, "value": "value of configuration" }, "permanent_deletion_allowlist_users": { "is_used": true, "value": [ { "id": 1234, "name": "John Doe", "email": "johndoe@box.com" } ] } }, "user_settings": { "enterprise_feature_settings": [ { "is_used": true, "value": { "id": "eyJlbnRlcnByaXNlSWQiOiIxMTExNDQyNzY3IiwiZmVhdHVyZUlkIjoiY2FudmFzIn0=", "feature": { "id": "canvas" }, "state": "enabled", "can_configure": true, "is_configured": true, "allowlist": [ { "type": "user", "id": "12345" } ], "denylist": [ { "type": "user", "id": "12345" } ] } } ], "user_invites_expiration_time_frame": { "is_used": true, "value": "7 days" }, "is_username_change_restricted": { "is_used": true, "value": true }, "is_box_sync_restricted_for_new_users": { "is_used": true, "value": true }, "is_view_all_users_enabled_for_new_users": { "is_used": true, "value": true }, "is_device_limit_exemption_enabled_for_new_users": { "is_used": true, "value": true }, "is_external_collaboration_restricted_for_new_users": { "is_used": true, "value": true }, "is_unlimited_storage_enabled_for_new_users": { "is_used": true, "value": true }, "new_user_default_storage_limit": { "is_used": true, "value": 1234 }, "new_user_default_timezone": { "is_used": true, "value": "GMT-05:00 America\\/Chicago CDT" }, "new_user_default_language": { "is_used": true, "value": "English (US)" }, "is_enterprise_sso_required": { "is_used": true, "value": true }, "is_enterprise_sso_in_testing": { "is_used": true, "value": true }, "is_sso_auto_add_groups_enabled": { "is_used": true, "value": true }, "is_sso_auto_add_user_to_groups_enabled": { "is_used": true, "value": true }, "is_sso_auto_remove_user_from_groups_enabled": { "is_used": true, "value": true }, "user_tracking_codes": { "is_used": true, "value": [ { "id": 1234, "name": "Employee ID" } ] }, "number_of_user_tracking_codes_remaining": { "is_used": true, "value": 1234 }, "is_instant_login_restricted": { "is_used": true, "value": true } }, "shield": { "shield_rules": [ { "id": "1234567890", "type": "shield_rule", "rule_category": "Malicious Content", "name": "Block malicious files", "description": "Blocks files identified as malicious content", "priority": "high", "created_at": "2012-12-12T10:53:43-08:00", "modified_at": "2012-12-12T10:53:43-08:00" } ] } } ``` ## Enterprise configuration content and sharing The enterprise configuration for the content and sharing category. | Property | Type | Required | Description | | - | - | - | - | | `enterprise_feature_settings` | array of `Enterprise feature settings item` | No | | | `sharing_item_type` | `Enterprise configuration item with string value` | No | The selected option for the type of items that sharing is enable for. | | `shared_link_company_definition` | `Enterprise configuration item with string value` | No | The definition of the company for shared links (such as `enterprise_id`, `users_with_email_domain`). | | `shared_link_access` | `Enterprise configuration item with string value` | No | The selected option for who has access to shared links. | | `shared_link_default_access` | `Enterprise configuration item with string value` | No | The selected option for the access level of shared links. | | `shared_link_default_permissions_selected` | `Enterprise configuration item` | No | The permissions selected for shared links for each type. | | `is_open_custom_urls_disabled` | `Enterprise configuration item with boolean value` | No | Disables custom shared link URLs for public access. | | `is_custom_domain_hidden_in_shared_link` | `Enterprise configuration item with boolean value` | No | Hides the custom domain in shared links. | | `collaboration_permissions` | `Enterprise configuration item` | No | | | `default_collaboration_role` | `Enterprise configuration item with string value` | No | The current default collaboration role. | | `is_invite_privilege_restricted` | `Enterprise configuration item with boolean value` | No | Restrict invites to only admin, folder owners, and co-owners. | | `collaboration_restrictions` | `Enterprise configuration item` | No | The collaboration restrictions for the enterprise. | | `is_collaborator_invite_links_disabled` | `Enterprise configuration item with boolean value` | No | Disables users to invite collaborators using invite links. | | `is_invite_group_collaborator_disabled` | `Enterprise configuration item with boolean value` | No | Disables users to invite group collaborators. | | `is_ownership_transfer_restricted` | `Enterprise configuration item with boolean value` | No | Prevents ownership transfer to external users. | | `external_collaboration_status` | `Enterprise configuration item` | No | The external collaboration status. Possible values: `enable_external_collaboration`, `limit_collaboration_to_allowlisted_domains`, `limit_collaboration_to_users_within_enterprise`. | | `external_collaboration_allowlist_users` | `Enterprise configuration item` | No | The external collaboration allowlist users. | | `is_watermarking_enterprise_feature_enabled` | `Enterprise configuration item with boolean value` | No | Enables watermarking enterprise feature. | | `is_root_content_creation_restricted` | `Enterprise configuration item with boolean value` | No | Only admins can create and delete first-level folders, files, and bookmarks. | | `is_tag_creation_restricted` | `Enterprise configuration item with boolean value` | No | Only selected users can create tags. | | `tag_creation_restriction` | `Enterprise configuration item with string value` | No | The selected option for tag creation restriction. | | `is_email_uploads_enabled` | `Enterprise configuration item with boolean value` | No | Enables email uploads. | | `is_custom_settings_enabled` | `Enterprise configuration item with boolean value` | No | Enables custom settings. | | `is_forms_login_required` | `Enterprise configuration item with boolean value` | No | Require uploaders to log in with a Box account for Forms. | | `is_forms_branding_default_enabled` | `Enterprise configuration item with boolean value` | No | Company brand color and logo defaults to Logo for use on Brand Color for Forms. | | `is_cc_free_trial_active` | `Enterprise configuration item with boolean value` | No | Indicates if the enterprise is on a free trial. | | `is_file_request_editors_allowed` | `Enterprise configuration item with boolean value` | No | Indicates if editors are allowed for file requests feature. | | `is_file_request_branding_default_enabled` | `Enterprise configuration item with boolean value` | No | Company brand color and logo defaults to Logo for use on Brand Color for File Requests. | | `is_file_request_login_required` | `Enterprise configuration item with boolean value` | No | Require uploaders to log in with a Box account for File Requests. | | `is_shared_links_expiration_enabled` | `Enterprise configuration item with boolean value` | No | Enables shared links expiration. | | `shared_links_expiration_days` | `Enterprise configuration item with integer value` | No | The expiration time for shared links. | | `is_public_shared_links_expiration_enabled` | `Enterprise configuration item with boolean value` | No | Enables public shared links expiration. | | `public_shared_links_expiration_days` | `Enterprise configuration item with integer value` | No | The expiration time for public shared links. | | `shared_expiration_target` | `Enterprise configuration item with string value` | No | The selected option of types for applying shared link expiration. | | `is_shared_links_expiration_notification_enabled` | `Enterprise configuration item with boolean value` | No | Enables shared links expiration notification. | | `shared_links_expiration_notification_days` | `Enterprise configuration item with integer value` | No | The number of days before expiration to notify users. | | `is_shared_links_expiration_notification_prevented` | `Enterprise configuration item with boolean value` | No | Prevents users from disabling shared links expiration notification. | | `is_auto_delete_enabled` | `Enterprise configuration item with boolean value` | No | Enables auto delete. | | `auto_delete_days` | `Enterprise configuration item with integer value` | No | The number of days before auto delete. | | `is_auto_delete_expiration_modification_prevented` | `Enterprise configuration item with boolean value` | No | Prevents users from modifying auto delete expiration. | | `auto_delete_target` | `Enterprise configuration item with string value` | No | The selected option of types for applying auto delete. | | `is_collaboration_expiration_enabled` | `Enterprise configuration item with boolean value` | No | Enables collaboration expiration. | | `collaboration_expiration_days` | `Enterprise configuration item with integer value` | No | The number of days before collaboration expiration. | | `is_collaboration_expiration_modification_prevented` | `Enterprise configuration item with boolean value` | No | Prevents users from modifying collaboration expiration. | | `is_collaboration_expiration_notification_enabled` | `Enterprise configuration item with boolean value` | No | Enables collaboration expiration notification. | | `collaboration_expiration_target` | `Enterprise configuration item with string value` | No | The selected option of types for applying collaboration expiration. | | `trash_auto_clear_time` | `Enterprise configuration item with integer value` | No | The time before items are permanently deleted from the trash in seconds. | | `permanent_deletion_access` | `Enterprise configuration item with string value` | No | The selected option for who can delete items from the trash. | | `permanent_deletion_allowlist_users` | `Enterprise configuration item` | No | The allowlist users for permanently deleting items from the trash. | ```json Example theme={null} { "enterprise_feature_settings": [ { "is_used": true, "value": { "id": "eyJlbnRlcnByaXNlSWQiOiIxMTExNDQyNzY3IiwiZmVhdHVyZUlkIjoiY2FudmFzIn0=", "feature": { "id": "canvas" }, "state": "enabled", "can_configure": true, "is_configured": true, "allowlist": [ { "type": "user", "id": "12345" } ], "denylist": [ { "type": "user", "id": "12345" } ] } } ], "sharing_item_type": { "is_used": true, "value": "value of configuration" }, "shared_link_company_definition": { "is_used": true, "value": "value of configuration" }, "shared_link_access": { "is_used": true, "value": "value of configuration" }, "shared_link_default_access": { "is_used": true, "value": "value of configuration" }, "shared_link_default_permissions_selected": { "is_used": true, "value": { "shared_links_option": "settings_shared_link_preview", "default_shared_link_type": "settings_default_shared_link_preview", "notes_shared_link_option": "settings_notes_option_to_edit", "default_notes_shared_link_type": "settings_default_notes_shared_link_to_edit" } }, "is_open_custom_urls_disabled": { "is_used": true, "value": true }, "is_custom_domain_hidden_in_shared_link": { "is_used": true, "value": true }, "collaboration_permissions": { "is_used": true, "value": { "is_co_owner_role_enabled": true, "is_editor_role_enabled": true, "is_previewer_role_enabled": true, "is_previewer_uploader_role_enabled": true, "is_uploader_role_enabled": true, "is_viewer_role_enabled": true, "is_viewer_uploader_role_enabled": true } }, "default_collaboration_role": { "is_used": true, "value": "value of configuration" }, "is_invite_privilege_restricted": { "is_used": true, "value": true }, "collaboration_restrictions": { "is_used": true, "value": [ "internal" ] }, "is_collaborator_invite_links_disabled": { "is_used": true, "value": true }, "is_invite_group_collaborator_disabled": { "is_used": true, "value": true }, "is_ownership_transfer_restricted": { "is_used": true, "value": true }, "external_collaboration_status": { "is_used": true, "value": "enable_external_collaboration" }, "external_collaboration_allowlist_users": { "is_used": true, "value": [ { "id": 1234, "name": "John Doe", "email": "johndoe@box.com" } ] }, "is_watermarking_enterprise_feature_enabled": { "is_used": true, "value": true }, "is_root_content_creation_restricted": { "is_used": true, "value": true }, "is_tag_creation_restricted": { "is_used": true, "value": true }, "tag_creation_restriction": { "is_used": true, "value": "value of configuration" }, "is_email_uploads_enabled": { "is_used": true, "value": true }, "is_custom_settings_enabled": { "is_used": true, "value": true }, "is_forms_login_required": { "is_used": true, "value": true }, "is_forms_branding_default_enabled": { "is_used": true, "value": true }, "is_cc_free_trial_active": { "is_used": true, "value": true }, "is_file_request_editors_allowed": { "is_used": true, "value": true }, "is_file_request_branding_default_enabled": { "is_used": true, "value": true }, "is_file_request_login_required": { "is_used": true, "value": true }, "is_shared_links_expiration_enabled": { "is_used": true, "value": true }, "shared_links_expiration_days": { "is_used": true, "value": 1234 }, "is_public_shared_links_expiration_enabled": { "is_used": true, "value": true }, "public_shared_links_expiration_days": { "is_used": true, "value": 1234 }, "shared_expiration_target": { "is_used": true, "value": "value of configuration" }, "is_shared_links_expiration_notification_enabled": { "is_used": true, "value": true }, "shared_links_expiration_notification_days": { "is_used": true, "value": 1234 }, "is_shared_links_expiration_notification_prevented": { "is_used": true, "value": true }, "is_auto_delete_enabled": { "is_used": true, "value": true }, "auto_delete_days": { "is_used": true, "value": 1234 }, "is_auto_delete_expiration_modification_prevented": { "is_used": true, "value": true }, "auto_delete_target": { "is_used": true, "value": "value of configuration" }, "is_collaboration_expiration_enabled": { "is_used": true, "value": true }, "collaboration_expiration_days": { "is_used": true, "value": 1234 }, "is_collaboration_expiration_modification_prevented": { "is_used": true, "value": true }, "is_collaboration_expiration_notification_enabled": { "is_used": true, "value": true }, "collaboration_expiration_target": { "is_used": true, "value": "value of configuration" }, "trash_auto_clear_time": { "is_used": true, "value": 1234 }, "permanent_deletion_access": { "is_used": true, "value": "value of configuration" }, "permanent_deletion_allowlist_users": { "is_used": true, "value": [ { "id": 1234, "name": "John Doe", "email": "johndoe@box.com" } ] } } ``` ## Enterprise configuration security The enterprise configuration for the security category. | Property | Type | Required | Description | | - | - | - | - | | `is_managed_user_signup_enabled` | `Enterprise configuration item with boolean value` | No | Determines if users are allowed to sign up on their own. | | `is_managed_user_signup_notification_enabled` | `Enterprise configuration item with boolean value` | No | Determines if users must sign up with your corporate email. | | `is_managed_user_signup_corporate_email_enabled` | `Enterprise configuration item with boolean value` | No | When enabled, admin are emailed when new users are added. | | `is_new_user_notification_daily_digest_enabled` | `Enterprise configuration item with boolean value` | No | Depends on `is_managed_user_signup_corporate_email_enabled`. A true value represents `In daily summary emails`. A false value represents `Immediately`. | | `is_managed_user_email_change_disabled` | `Enterprise configuration item with boolean value` | No | When enabled, prevents users from changing their primary email address. | | `is_multi_factor_auth_required` | `Enterprise configuration item with boolean value` | No | Determines if multi-factor authentication is required for all managed users. | | `is_weak_password_prevention_enabled` | `Enterprise configuration item with boolean value` | No | When enabled, prevents common words / email address to be used in a password. | | `is_password_leak_detection_enabled` | `Enterprise configuration item with boolean value` | No | Enables exposed password detection. | | `last_password_reset_at` | `Enterprise configuration item` | No | | | `is_password_request_notification_enabled` | `Enterprise configuration item with boolean value` | No | When enabled, admins are notified when users request a forget password email. | | `is_password_change_notification_enabled` | `Enterprise configuration item with boolean value` | No | When enabled, admins are notified when users change passwords in Settings. | | `is_strong_password_for_ext_collab_enabled` | `Enterprise configuration item with boolean value` | No | Requires strong passwords for External Collaborators. | | `is_managed_user_migration_disabled` | `Enterprise configuration item with boolean value` | No | Disables users from being migrated in or out of the enterprise. | | `join_link` | `Enterprise configuration item with string value` | No | Join url for self sign up. Depends on `is_managed_user_signup_enabled`. Applies if `join_url` is not used. | | `join_url` | `Enterprise configuration item with string value` | No | Join url for self sign up. Depends on `is_managed_user_signup_enabled`. Applies if `join_link` is not used. | | `failed_login_attempts_to_trigger_admin_notification` | `Enterprise configuration item with integer value` | No | Number of failed login attempts before the primary admin is notified. | | `password_min_length` | `Enterprise configuration item with integer value` | No | Minimum number of characters required for passwords. | | `password_min_uppercase_characters` | `Enterprise configuration item with integer value` | No | Minimum number of upper case characters required in passwords. | | `password_min_numeric_characters` | `Enterprise configuration item with integer value` | No | Minimum number of numbers required in passwords. | | `password_min_special_characters` | `Enterprise configuration item with integer value` | No | Minimum number of symbols required in passwords. | | `password_reset_frequency` | `Enterprise configuration item with string value` | No | Determines if users must reset their passwords every specified time period. | | `previous_password_reuse_limit` | `Enterprise configuration item with string value` | No | Number of previous passwords users are prevented from using. | | `session_duration` | `Enterprise configuration item with string value` | No | Value indicating time from the last activity a user can remain logged in without activity before being logged out. | | `external_collab_multi_factor_auth_settings` | `Enterprise configuration item` | No | External Collaborators multi-factor authentication settings. | | `keysafe` | `Enterprise configuration item` | No | | | `is_custom_session_duration_enabled` | `Enterprise configuration item with boolean value` | No | Enables custom session duration for select users. | | `custom_session_duration_value` | `Enterprise configuration item with string value` | No | Value indicating duration users from specified groups can remain logged in without activity before being logged out. | | `custom_session_duration_groups` | `Enterprise configuration item` | No | Groups for custom session duration. | | `multi_factor_auth_type` | `Enterprise configuration item with string value` | No | Managed Users multi-factor authentication requirement method type. | | `enforced_mfa_frequency` | `Enterprise configuration item` | No | Managed Users multi-factor authentication frequency. The default authentication frequency is set to 30 days, with authentication time calculated from the most recent successful MFA login. | ```json Example theme={null} { "is_managed_user_signup_enabled": { "is_used": true, "value": true }, "is_managed_user_signup_notification_enabled": { "is_used": true, "value": true }, "is_managed_user_signup_corporate_email_enabled": { "is_used": true, "value": true }, "is_new_user_notification_daily_digest_enabled": { "is_used": true, "value": true }, "is_managed_user_email_change_disabled": { "is_used": true, "value": true }, "is_multi_factor_auth_required": { "is_used": true, "value": true }, "is_weak_password_prevention_enabled": { "is_used": true, "value": true }, "is_password_leak_detection_enabled": { "is_used": true, "value": true }, "last_password_reset_at": { "is_used": true, "value": "2012-12-12T10:53:43-08:00" }, "is_password_request_notification_enabled": { "is_used": true, "value": true }, "is_password_change_notification_enabled": { "is_used": true, "value": true }, "is_strong_password_for_ext_collab_enabled": { "is_used": true, "value": true }, "is_managed_user_migration_disabled": { "is_used": true, "value": true }, "join_link": { "is_used": true, "value": "value of configuration" }, "join_url": { "is_used": true, "value": "value of configuration" }, "failed_login_attempts_to_trigger_admin_notification": { "is_used": true, "value": 1234 }, "password_min_length": { "is_used": true, "value": 1234 }, "password_min_uppercase_characters": { "is_used": true, "value": 1234 }, "password_min_numeric_characters": { "is_used": true, "value": 1234 }, "password_min_special_characters": { "is_used": true, "value": 1234 }, "password_reset_frequency": { "is_used": true, "value": "value of configuration" }, "previous_password_reuse_limit": { "is_used": true, "value": "value of configuration" }, "session_duration": { "is_used": true, "value": "value of configuration" }, "external_collab_multi_factor_auth_settings": { "is_used": true, "value": { "denylist_domains": [ "example.com" ], "denylist_emails": [ "spam@example.com" ], "allowlist_domains": [ "example.com" ], "allowlist_emails": [ "mail@example.com" ], "state": "enabled", "scheduled_status": "inprogress", "scheduled_at": "2012-12-12T10:53:43-08:00", "factor_type_settings": "totp" } }, "keysafe": { "is_used": true, "value": { "keysafe_enabled": true, "cloud_provider": "", "key_id": "1234", "account_id": "1234", "location_id": "1234", "project_id": "1234", "keyring_id": "1234" } }, "is_custom_session_duration_enabled": { "is_used": true, "value": true }, "custom_session_duration_value": { "is_used": true, "value": "value of configuration" }, "custom_session_duration_groups": { "is_used": true, "value": [ { "id": "1234", "name": "Group Name" } ] }, "multi_factor_auth_type": { "is_used": true, "value": "value of configuration" }, "enforced_mfa_frequency": { "is_used": true, "value": { "days": 30, "hours": 24 } } } ``` ## Enterprise configuration shield The enterprise configuration for the shield category. | Property | Type | Required | Description | | - | - | - | - | | `shield_rules` | array of `Shield rule item` | No | The shield rules configuration for the enterprise. | ```json Example theme={null} { "shield_rules": [ { "id": "1234567890", "type": "shield_rule", "rule_category": "Malicious Content", "name": "Block malicious files", "description": "Blocks files identified as malicious content", "priority": "high", "created_at": "2012-12-12T10:53:43-08:00", "modified_at": "2012-12-12T10:53:43-08:00" } ] } ``` ## Enterprise configuration user settings The enterprise configuration for the user settings category. | Property | Type | Required | Description | | - | - | - | - | | `enterprise_feature_settings` | array of `Enterprise feature settings item` | No | | | `user_invites_expiration_time_frame` | `Enterprise configuration item with string value` | No | Determines the time frame for any unaccepted invitation to your Box enterprise account to expire. | | `is_username_change_restricted` | `Enterprise configuration item with boolean value` | No | Determines if users are restricted from changing their username in Account Settings. | | `is_box_sync_restricted_for_new_users` | `Enterprise configuration item with boolean value` | No | Determines if Box Sync is restricted for new users. | | `is_view_all_users_enabled_for_new_users` | `Enterprise configuration item with boolean value` | No | Determines if new users can see all managed users in their contacts when inviting collaborators. | | `is_device_limit_exemption_enabled_for_new_users` | `Enterprise configuration item with boolean value` | No | Determines if new users are exempt from the maximum number of devices specified in the Device Protection settings. | | `is_external_collaboration_restricted_for_new_users` | `Enterprise configuration item with boolean value` | No | Determines if new users will not be allowed to create folders that can have external collaborators. | | `is_unlimited_storage_enabled_for_new_users` | `Enterprise configuration item with boolean value` | No | Determines if new users are given unlimited storage. | | `new_user_default_storage_limit` | `Enterprise configuration item with integer value` | No | Determines the default storage limit for new users. Does not apply if `is_unlimited_storage_enabled_for_new_users` is true. | | `new_user_default_timezone` | `Enterprise configuration item with string value` | No | Determines the default timezone for new users. | | `new_user_default_language` | `Enterprise configuration item with string value` | No | Determines the default language for new users. | | `is_enterprise_sso_required` | `Enterprise configuration item with boolean value` | No | Value indicating if SSO is required for all users. | | `is_enterprise_sso_in_testing` | `Enterprise configuration item with boolean value` | No | Value indicating if SSO is in testing mode for all users. | | `is_sso_auto_add_groups_enabled` | `Enterprise configuration item with boolean value` | No | Determines if any new groups received for a user upon login through SSO but not present in the enterprise will be auto added to the enterprise. | | `is_sso_auto_add_user_to_groups_enabled` | `Enterprise configuration item with boolean value` | No | Determines if a user will be added to enterprise groups received upon user login through SSO. | | `is_sso_auto_remove_user_from_groups_enabled` | `Enterprise configuration item with boolean value` | No | Determines if a user will be removed from enterprise groups upon user login through SSO. | | `user_tracking_codes` | `Enterprise configuration item` | No | Properties that are assigned as additional values to users, like employee ID or region. | | `number_of_user_tracking_codes_remaining` | `Enterprise configuration item with integer value` | No | The remaining number of `user_tracking_codes` that can be created. | | `is_instant_login_restricted` | `Enterprise configuration item with boolean value` | No | Determines if users are restricted from granting (limited) access for Box Support for troubleshooting. | ```json Example theme={null} { "enterprise_feature_settings": [ { "is_used": true, "value": { "id": "eyJlbnRlcnByaXNlSWQiOiIxMTExNDQyNzY3IiwiZmVhdHVyZUlkIjoiY2FudmFzIn0=", "feature": { "id": "canvas" }, "state": "enabled", "can_configure": true, "is_configured": true, "allowlist": [ { "type": "user", "id": "12345" } ], "denylist": [ { "type": "user", "id": "12345" } ] } } ], "user_invites_expiration_time_frame": { "is_used": true, "value": "7 days" }, "is_username_change_restricted": { "is_used": true, "value": true }, "is_box_sync_restricted_for_new_users": { "is_used": true, "value": true }, "is_view_all_users_enabled_for_new_users": { "is_used": true, "value": true }, "is_device_limit_exemption_enabled_for_new_users": { "is_used": true, "value": true }, "is_external_collaboration_restricted_for_new_users": { "is_used": true, "value": true }, "is_unlimited_storage_enabled_for_new_users": { "is_used": true, "value": true }, "new_user_default_storage_limit": { "is_used": true, "value": 1234 }, "new_user_default_timezone": { "is_used": true, "value": "GMT-05:00 America\\/Chicago CDT" }, "new_user_default_language": { "is_used": true, "value": "English (US)" }, "is_enterprise_sso_required": { "is_used": true, "value": true }, "is_enterprise_sso_in_testing": { "is_used": true, "value": true }, "is_sso_auto_add_groups_enabled": { "is_used": true, "value": true }, "is_sso_auto_add_user_to_groups_enabled": { "is_used": true, "value": true }, "is_sso_auto_remove_user_from_groups_enabled": { "is_used": true, "value": true }, "user_tracking_codes": { "is_used": true, "value": [ { "id": 1234, "name": "Employee ID" } ] }, "number_of_user_tracking_codes_remaining": { "is_used": true, "value": 1234 }, "is_instant_login_restricted": { "is_used": true, "value": true } } ``` # External Users resources Source: https://developer.box.com/reference/v2025.0/external-users-resources External users are collaborators from outside of your enterprise. ## External Users Submit Delete Job Response Multi-status response containing the result for each external user deletion request. | Property | Type | Required | Description | | - | - | - | - | | `entries` | array of `External User Deletion Result` | Yes | Array of results of each external user deletion request. | ```json Example theme={null} { "entries": [ { "user_id": "12345", "status": 202, "detail": "string" } ] } ``` # Box Hub Collaborations resources Source: https://developer.box.com/reference/v2025.0/hub-collaborations-resources A set of endpoints used to manage collaborations within a Box Hub. ## Box Hub Collaboration A Box Hub collaboration object grants a user or group access to a Box Hub with permissions defined by a specific role. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier for this collaboration. | | `type` | `enum` | Yes | The value will always be `hub_collaboration`. Available options: `hub_collaboration`. | | `hub` | [Box Hub (Base)](/reference/v2025.0/hubs-resources#box-hub-base) | No | | | `accessible_by` | `Box Hub Accessible By` | No | | | `role` | `string` | No | The level of access granted to a Box Hub. Possible values are `editor`, `viewer`, and `co-owner`. | | `status` | `enum` | No | The status of the collaboration invitation. If the status is `pending`, `login` and `name` return an empty string. Available options: `accepted`, `pending`, `rejected`. | | `acceptance_requirements_status` | `object` | No | | ```json Example theme={null} { "id": "12345678", "type": "hub_collaboration", "hub": { "id": "12345", "type": "hubs" }, "accessible_by": { "id": "11446498", "type": "user", "name": "Aaron Levie", "login": "ceo@example.com" }, "role": "editor", "status": "accepted", "acceptance_requirements_status": { "terms_of_service_requirement": { "is_accepted": true, "terms_of_service": { "id": "11446498", "type": "terms_of_service" } }, "strong_password_requirement": { "enterprise_has_strong_password_required_for_external_users": true, "user_has_strong_password": true }, "two_factor_authentication_requirement": { "enterprise_has_two_factor_auth_enabled": true, "user_has_two_factor_authentication_enabled": true } } } ``` ## Box Hub Collaborations A list of Box Hub collaborations. | Property | Type | Required | Description | | - | - | - | - | | `entries` | array of [Box Hub Collaboration](#box-hub-collaboration) | No | A list of Box Hub collaborations. | | `limit` | `integer` | No | The limit that was used for these entries. This will be the same as the `limit` query parameter unless that value exceeded the maximum value allowed. The maximum value varies by API. | | `next_marker` | `string` | No | The marker for the start of the next page of results. Can be `null`. | ```json Example theme={null} { "entries": [ { "id": "12345678", "type": "hub_collaboration", "hub": { "id": "12345", "type": "hubs" }, "accessible_by": { "id": "11446498", "type": "user", "name": "Aaron Levie", "login": "ceo@example.com" }, "role": "editor", "status": "accepted", "acceptance_requirements_status": { "terms_of_service_requirement": { "is_accepted": true, "terms_of_service": { "id": "11446498", "type": "terms_of_service" } }, "strong_password_requirement": { "enterprise_has_strong_password_required_for_external_users": true, "user_has_strong_password": true }, "two_factor_authentication_requirement": { "enterprise_has_two_factor_auth_enabled": true, "user_has_two_factor_authentication_enabled": true } } } ], "limit": 1000, "next_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVii" } ``` # Box Hub Document resources Source: https://developer.box.com/reference/v2025.0/hub-document-resources A set of endpoints used to retrieve Box Hub Document elements (pages and content blocks). ## Box Hub Callout Box Text Block A callout box block in the Box Hub Document. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier for this block. | | `parent_id` | `string` | No | The unique identifier of the parent block. Null for direct children of the page. Can be `null`. | | `type` | `enum` | Yes | The type of this block. The value is always `callout_box`. Available options: `callout_box`. | | `fragment` | `string` | Yes | Text content of the block. Includes rich text formatting. | ```json Example theme={null} { "id": "c9588f08-22d7-4d17-8ca9-f1e61c98c0bd", "parent_id": "d97f7c26-0d9b-42a1-a00d-6e6db9619e0f", "type": "callout_box", "fragment": "This is the callout box" } ``` ## Box Hub Divider Block A divider block in the Box Hub Document. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier for this block. | | `parent_id` | `string` | No | The unique identifier of the parent block. Null for direct children of the page. Can be `null`. | | `type` | `enum` | Yes | The type of this block. The value is always `divider`. Available options: `divider`. | ```json Example theme={null} { "id": "c9588f08-22d7-4d17-8ca9-f1e61c98c0bd", "parent_id": "d97f7c26-0d9b-42a1-a00d-6e6db9619e0f", "type": "divider" } ``` ## Box Hub Document Blocks A list of Hub Document blocks. | Property | Type | Required | Description | | - | - | - | - | | `entries` | array of `Box Hub Document Block Entry` | Yes | Ordered list of blocks. | | `type` | `enum` | Yes | The value will always be `document_blocks`. Available options: `document_blocks`. | | `limit` | `integer` | No | The limit that was used for these entries. This will be the same as the `limit` query parameter unless that value exceeded the maximum value allowed. The maximum value varies by API. | | `next_marker` | `string` | No | The marker for the start of the next page of results. Can be `null`. | ```json Example theme={null} { "entries": [ { "id": "c9588f08-22d7-4d17-8ca9-f1e61c98c0bd", "parent_id": "d97f7c26-0d9b-42a1-a00d-6e6db9619e0f", "type": "paragraph", "fragment": "This is the paragraph" } ], "type": "document_blocks", "limit": 1000, "next_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVii" } ``` ## Box Hub Document Page A Page in the Box Hub Document. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier for this page. | | `type` | `string` | Yes | The type of this resource. The value is always `page`. | | `parent_id` | `string` | No | The unique identifier of the parent page. Null for root-level pages. Can be `null`. | | `title_fragment` | `string` | Yes | The title text of the page. Includes rich text formatting. | ```json Example theme={null} { "id": "d97f7c26-0d9b-42a1-a00d-6e6db9619e0f", "type": "page", "parent_id": "d97f7c26-0d9b-42a1-a00d-6e6db9619e0f", "title_fragment": "This is the home page title" } ``` ## Box Hub Document Pages A list of Hub Document Pages. | Property | Type | Required | Description | | - | - | - | - | | `entries` | array of [Box Hub Document Page](#box-hub-document-page) | Yes | Ordered list of pages. | | `type` | `enum` | Yes | The value will always be `document_pages`. Available options: `document_pages`. | | `limit` | `integer` | No | The limit that was used for these entries. This will be the same as the `limit` query parameter unless that value exceeded the maximum value allowed. The maximum value varies by API. | | `next_marker` | `string` | No | The marker for the start of the next page of results. Can be `null`. | ```json Example theme={null} { "entries": [ { "id": "d97f7c26-0d9b-42a1-a00d-6e6db9619e0f", "type": "page", "parent_id": "d97f7c26-0d9b-42a1-a00d-6e6db9619e0f", "title_fragment": "This is the home page title" } ], "type": "document_pages", "limit": 1000, "next_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVii" } ``` ## Box Hub Item List Block An item list block in the Box Hub Document. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier for this block. | | `parent_id` | `string` | No | The unique identifier of the parent block. Null for direct children of the page. Can be `null`. | | `type` | `enum` | Yes | The type of this block. The value is always `item_list`. Available options: `item_list`. | ```json Example theme={null} { "id": "c9588f08-22d7-4d17-8ca9-f1e61c98c0bd", "parent_id": "d97f7c26-0d9b-42a1-a00d-6e6db9619e0f", "type": "item_list" } ``` ## Box Hub Paragraph Text Block A paragraph block in the Box Hub Document. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier for this block. | | `parent_id` | `string` | No | The unique identifier of the parent block. Null for direct children of the page. Can be `null`. | | `type` | `enum` | Yes | The type of this block. The value is always `paragraph`. Available options: `paragraph`. | | `fragment` | `string` | Yes | Text content of the block. Includes rich text formatting. | ```json Example theme={null} { "id": "c9588f08-22d7-4d17-8ca9-f1e61c98c0bd", "parent_id": "d97f7c26-0d9b-42a1-a00d-6e6db9619e0f", "type": "paragraph", "fragment": "This is the paragraph" } ``` ## Box Hub Section Title Text Block A section title block in the Box Hub Document. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier for this block. | | `parent_id` | `string` | No | The unique identifier of the parent block. Null for direct children of the page. Can be `null`. | | `type` | `enum` | Yes | The type of this block. The value is always `section_title`. Available options: `section_title`. | | `fragment` | `string` | Yes | Text content of the block. Includes rich text formatting. | ```json Example theme={null} { "id": "c9588f08-22d7-4d17-8ca9-f1e61c98c0bd", "parent_id": "d97f7c26-0d9b-42a1-a00d-6e6db9619e0f", "type": "section_title", "fragment": "This is the section title" } ``` # Box Hub Items resources Source: https://developer.box.com/reference/v2025.0/hub-items-resources A set of endpoints used to manage items within a Box Hub. ## Box Hub Item A Box Hub Item is a Box Item that is referenced in a Box Hub. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier for this item. | | `type` | `enum` | Yes | The type of the item. Available options: `file`, `folder`, `web_link`. | | `name` | `string` | Yes | The name of the item. | ```json Example theme={null} { "id": "12345678", "type": "file", "name": "My File" } ``` ## Box Hub Items A list of Box Hub items. | Property | Type | Required | Description | | - | - | - | - | | `entries` | array of [Box Hub Item](#box-hub-item) | No | A list of Box Hub items. | | `limit` | `integer` | No | The limit that was used for these entries. This will be the same as the `limit` query parameter unless that value exceeded the maximum value allowed. The maximum value varies by API. | | `next_marker` | `string` | No | The marker for the start of the next page of results. Can be `null`. | ```json Example theme={null} { "entries": [ { "id": "12345678", "type": "file", "name": "My File" } ], "limit": 1000, "next_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVii" } ``` ## Box Hub Items Manage Response Response schema for the status of Box Hub items management operations. | Property | Type | Required | Description | | - | - | - | - | | `operations` | array of `Box Hub Item Operation Result` | Yes | List of operations performed on Box Hub items. | ```json Example theme={null} { "operations": [ { "action": "add", "item": { "type": "file", "id": "42037322" }, "parent_id": "721d97d7-ac8a-4e5e-adb6-ef11af4e6d9e", "status": 200, "error": "Item not found" } ] } ``` # Box Hubs resources Source: https://developer.box.com/reference/v2025.0/hubs-resources A set of endpoints used to manage Box Hubs. ## Box Hub A standard representation of a Box Hub, as returned from any Box Hubs API endpoints by default. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier that represent a Box Hub. The ID for any Box Hub can be determined by visiting a Box Hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. | | `type` | `enum` | Yes | The value will always be `hubs`. Available options: `hubs`. | | `title` | `string` | No | The title given to the Box Hub. | | `description` | `string` | No | The description of the Box Hub. First 200 characters are returned. | | `created_at` | `string` | No | The date and time when the folder was created. This value may be `null` for some folders such as the root folder or the trash folder. | | `updated_at` | `string` | No | The date and time when the Box Hub was last updated. | | `created_by` | `User (Mini)` | No | The user who created this Box Hub. | | `updated_by` | `User (Mini)` | No | The user who last modified this Box Hub. | | `view_count` | `integer` | No | The number of views for the Box Hub. | | `is_ai_enabled` | `boolean` | No | Indicates if AI features are enabled for the Box Hub. | | `is_collaboration_restricted_to_enterprise` | `boolean` | No | Indicates if collaboration is restricted to the enterprise. | | `can_non_owners_invite` | `boolean` | No | Indicates if non-owners can invite others to the Box Hub. | | `can_shared_link_be_created` | `boolean` | No | Indicates if a shared link can be created for the Box Hub. | | `can_public_shared_link_be_created` | `boolean` | No | Indicates if a public shared link can be created for the Box Hub. | | `copy_hub_access` | `enum` | No | Specifies who is allowed to copy the Box Hub. - `all` - Any user with access to the Hub can copy it. - `company` - Only users within the same enterprise as the Hub can copy it. - `none` - No one can copy the Hub. Available options: `all`, `company`, `none`. | ```json Example theme={null} { "id": "12345", "type": "hubs", "title": "Contracts", "description": "All the contracts for the company.", "created_at": "2012-12-12T10:53:43Z", "updated_at": "2012-12-12T10:53:43Z", "created_by": { "id": "11446498", "type": "user", "name": "Aaron Levie", "login": "ceo@example.com" }, "updated_by": { "id": "11446498", "type": "user", "name": "Aaron Levie", "login": "ceo@example.com" }, "view_count": 506, "is_ai_enabled": true, "is_collaboration_restricted_to_enterprise": true, "can_non_owners_invite": true, "can_shared_link_be_created": true, "can_public_shared_link_be_created": true, "copy_hub_access": "company" } ``` ## Box Hub (Base) The bare basic representation of a Box Hub. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier that represent a Box Hub. The ID for any Box Hub can be determined by visiting a Box Hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. | | `type` | `enum` | Yes | The value will always be `hubs`. Available options: `hubs`. | ```json Example theme={null} { "id": "12345", "type": "hubs" } ``` ## Box Hubs A paginated list of hubs. | Property | Type | Required | Description | | - | - | - | - | | `entries` | array of [Box Hub](#box-hub) | No | A list of hubs. | | `limit` | `integer` | No | The limit that was used for these entries. This will be the same as the `limit` query parameter unless that value exceeded the maximum value allowed. The maximum value varies by API. | | `next_marker` | `string` | No | The marker for the start of the next page of results. Can be `null`. | ```json Example theme={null} { "entries": [ { "id": "12345", "type": "hubs", "title": "Contracts", "description": "All the contracts for the company.", "created_at": "2012-12-12T10:53:43Z", "updated_at": "2012-12-12T10:53:43Z", "created_by": { "id": "11446498", "type": "user", "name": "Aaron Levie", "login": "ceo@example.com" }, "updated_by": { "id": "11446498", "type": "user", "name": "Aaron Levie", "login": "ceo@example.com" }, "view_count": 506, "is_ai_enabled": true, "is_collaboration_restricted_to_enterprise": true, "can_non_owners_invite": true, "can_shared_link_be_created": true, "can_public_shared_link_be_created": true, "copy_hub_access": "company" } ], "limit": 1000, "next_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVii" } ``` # Shield lists resources Source: https://developer.box.com/reference/v2025.0/shield-lists-resources Shield List allow an administrator to create a list which will be shared between different Shield Smart Access and Threat Detection rules. ## List of Shield Lists List of Shield List objects. | Property | Type | Required | Description | | - | - | - | - | | `entries` | array of [Shield List (Mini)](#shield-list-mini) | No | A list of shield list objects. | ```json Example theme={null} { "entries": [ { "id": "12345678-1234-1234-1234-123456789012", "type": "shield_list", "name": "My Shield List", "content": { "type": "country_code" } } ] } ``` ## Shield List A standard representation of a Shield List. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | Unique identifier for the shield list. | | `type` | `string` | Yes | Type of the object. | | `name` | `string` | Yes | Name of the shield list. | | `enterprise` | `Enterprise reference` | Yes | Enterprise for which this list belongs to. | | `description` | `string` | No | Description of Shield List. | | `created_at` | `string` | Yes | ISO date time string when this shield list object was created. | | `updated_at` | `string` | Yes | ISO date time string when this shield list object was updated. | | `content` | `Shield List Content` | Yes | Specifies the type of elements in this Shield List and includes the elements themselves. | ```json Example theme={null} { "id": "90fb0e17-c332-40ed-b4f9-fa8908fbbb24", "type": "shield_list", "name": "Shield List Name", "enterprise": { "id": "1910967", "type": "enterprise" }, "description": "A list of things that are shielded", "created_at": "2025-01-13T15:31:01.896Z", "updated_at": "2025-01-13T15:31:01.896Z", "content": { "type": "country", "country_codes": [ "US", "PL" ] } } ``` ## Shield List (Mini) A mini representation of a Shield List. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | Unique global identifier for this list. | | `type` | `enum` | Yes | The type of object. Available options: `shield_list`. | | `name` | `string` | Yes | Name of Shield List. | | `content` | `object` | Yes | | ```json Example theme={null} { "id": "12345678-1234-1234-1234-123456789012", "type": "shield_list", "name": "My Shield List", "content": { "type": "country_code" } } ``` ## Shield List Content (Country) Representation of content of a Shield List that contains countries data. | Property | Type | Required | Description | | - | - | - | - | | `type` | `enum` | Yes | The type of content in the shield list. Available options: `country`. | | `country_codes` | array of `string` | Yes | List of country codes values. | ```json Example theme={null} { "type": "country", "country_codes": [ "US", "PL" ] } ``` ## Shield List Content (Domain) Representation of content of a Shield List that contains domains data. | Property | Type | Required | Description | | - | - | - | - | | `type` | `enum` | Yes | The type of content in the shield list. Available options: `domain`. | | `domains` | array of `string` | Yes | List of domain. | ```json Example theme={null} { "type": "domain", "domains": [ "box.com", "example.com" ] } ``` ## Shield List Content (Email) Representation of content of a Shield List that contains email addresses data. | Property | Type | Required | Description | | - | - | - | - | | `type` | `enum` | Yes | The type of content in the shield list. Available options: `email`. | | `email_addresses` | array of `string` | Yes | List of emails. | ```json Example theme={null} { "type": "email", "email_addresses": [ "example1@box.com", "example2@gmail.com" ] } ``` ## Shield List Content (Integration) Representation of content of a Shield List that contains integrations data. | Property | Type | Required | Description | | - | - | - | - | | `type` | `enum` | Yes | The type of content in the shield list. Available options: `integration`. | | `integrations` | array of `object` | Yes | List of integration. | ```json Example theme={null} { "type": "integration", "integrations": [ { "id": "505e40b5-5d04-446a-9268-72a0ccc13669" } ] } ``` ## Shield List Content (IP) Representation of content of a Shield List that contains ip addresses data. | Property | Type | Required | Description | | - | - | - | - | | `type` | `enum` | Yes | The type of content in the shield list. Available options: `ip`. | | `ip_addresses` | array of `string` | Yes | List of ip addresses and CIDRs. | ```json Example theme={null} { "type": "ip", "ip_addresses": [ "127.0.0.1", "80.12.12.12/24" ] } ``` # Convert to Box note resources Source: https://developer.box.com/reference/v2026.0/notes-resources Create Box Notes from supported source content (for example Markdown). ## Notes convert response Identifies the created Box Note file. | Property | Type | Required | Description | | - | - | - | - | | `type` | `enum` | Yes | The Box resource type; always `file` for a Box file. Available options: `file`. | | `id` | `string` | Yes | Box file ID of the created `.boxnote` file. | ```json Example theme={null} { "type": "file", "id": "1234567890" } ``` # Query resources Source: https://developer.box.com/reference/v2026.0/query-resources Query for your items. ## Query insight entry A single computed insight entry, containing its grouping keys (if applicable) and the computed metrics. | Property | Type | Required | Description | | - | - | - | - | | `key` | array of `string` | Yes | The grouping key values associated with the entry. Contains one value per `group_by` field for `group` entries, and is empty for `overall` and `other` entries. | | `type` | `enum` | Yes | The type of insight entry, indicating how the associated metrics are aggregated. Available options: `group`, `overall`, `other`. | | `metrics` | `object` | Yes | A map of metric aliases to their computed results. For `other` entries, the count is reported under the `totalCountBeyondTopGroups` key. | ```json Example theme={null} { "key": [ "ContractType1" ], "type": "group", "metrics": { "totalContractValue": { "type": "sum", "values": { "sum": 180000 } }, "countContractType": { "type": "count", "values": { "count": 245 } } } } ``` ## Query insights The computed results of an insights request, as a list of insight entries. | Property | Type | Required | Description | | - | - | - | - | | `insights` | array of [Query insight entry](#query-insight-entry) | Yes | The list of computed insight entries. Each entry corresponds to a group, the overall dataset, or the aggregate of groups outside the top results. | ```json Example theme={null} { "insights": [ { "key": [ "ContractType1" ], "type": "group", "metrics": { "totalContractValue": { "type": "sum", "values": { "sum": 180000 } }, "countContractType": { "type": "count", "values": { "count": 245 } } } }, { "key": [ "ContractType4" ], "type": "group", "metrics": { "totalContractValue": { "type": "sum", "values": { "sum": 100000 } }, "countContractType": { "type": "count", "values": { "count": 185 } } } }, { "key": [ "ContractType2" ], "type": "group", "metrics": { "totalContractValue": { "type": "sum", "values": { "sum": 200000 } }, "countContractType": { "type": "count", "values": { "count": 150 } } } }, { "key": [], "type": "other", "metrics": { "totalCountBeyondTopGroups": { "type": "count", "values": { "count": 165 } } } } ] } ``` ## Query result entry A single item matching the query. Always includes the item `type` and `id`. If a `fields` parameter was specified in the request, then additional item and/or metadata fields will be provided. | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | Yes | The unique identifier of the matching item. | | `type` | `string` | Yes | The type of the matching item. | ```json Example theme={null} { "id": "12345", "type": "file" } ``` ## Query results A paginated list of items matching the query, using milestone-based marker pagination. | Property | Type | Required | Description | | - | - | - | - | | `entries` | array of [Query result entry](#query-result-entry) | Yes | The list of items matching the query predicate. | | `next_marker` | `string` | Yes | The marker for the start of the next page of results. When `null`, there are no further results available. Can be `null`. | | `limit` | `integer` | Yes | The limit that was used for this request. This will be the same as the limit query parameter unless that value exceeded the maximum value allowed. | ```json Example theme={null} { "entries": [ { "id": "12345", "type": "file", "box": { "item": { "name": "My Form" } }, "enterprise_12345678": { "book": { "$parent": "file_12345", "$scope": "enterprise_12345678", "$template": "book", "$version": 4, "isbn": "1871263670", "purchasePrice": 234.57, "publicationYear": "1937" } } } ], "next_marker": "xppy0jjG1kBRSc7NBBSgQmBz1Gk6VaQdg5Vyb+Ob0iA=", "limit": 1 } ```