# 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 a `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 a `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 APIs 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 APIs 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 The Box API is a restful API that follow 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. ## 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 an `marker` parameter that equals 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 # Offset-based Pagination Source: https://developer.box.com/guides/api-calls/pagination/offset-based 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 # Errors Source: https://developer.box.com/guides/api-calls/permissions-and-errors/common-errors The Box APIs uses [HTTP status codes][status-codes] to communicate if a request has been successfully processed or not. 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, and some server errors in the HTTP 5XX range returns 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 solution 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** | The `item.type` field of the collaboration item should be set 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 may 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 may 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_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 may 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 may 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 may 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. More information is available [here](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 that you are attempting to collaborate in on an item is already collaborated on that item. This request is not needed. | ### 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 may 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. See [here](https://support.box.com/hc/en-us/articles/360043693434-API-Content-API-403-access-denied-insufficient-permissions-Errors) for solution information. | | | | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **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** | See [here](https://support.box.com/hc/en-us/articles/360043697314-Understand-the-Maximum-File-Size-You-Can-Upload-to-Box) for maximum file size limits based on account type. | | | | | **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 that is simply moved to the trash will still count 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 may be produced 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 / modified is unique. | | | | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Error** | `name_temporarily_reserved` | | **Message** | The item name is reserved by another processing item. Wait 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. Allow the first request to complete before sending the second. | | | | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | **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 may arise 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 requested 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. | ### 500 Internal Service 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 may 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 # Permissions and errors Source: https://developer.box.com/guides/api-calls/permissions-and-errors/index The following guides help you understand and resolve permission and error conditions you may encounter when working with the Box API. ## Reference * Errors — the full catalog of Box error codes, grouped by HTTP status. * Rate limits — the different rate limits Box applies and how to handle `429 Too Many Requests`. * Scopes — the scopes your application can request and what each one grants. * Token and URL expiration — how long tokens, authorization codes, and download URLs stay valid. * Versioning errors — errors related to API and SDK versioning. ## Troubleshooting To diagnose a failing application, see the Troubleshooting guides, including Debugging your Box app and the App Diagnostics Report. # Rate Limits Source: https://developer.box.com/guides/api-calls/permissions-and-errors/rate-limits 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.  ## 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 a 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. [pricing]: https://www.box.com/pricing # Scopes Source: https://developer.box.com/guides/api-calls/permissions-and-errors/scopes 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 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 you 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 scope. ### 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. 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. 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 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 enterprise properties, for client-side applications, the Access Token used must must be associated with an Admin Co-Admin with the correct permissions. ### 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 get workflows 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 view and create retention policies with Box Governance. This requires the enterprise to have purchased Box Governance. 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 | Allow user to edit & delete annotations | | `annotation_view_all` | Preview | Allows user to view annotations by all users | | `annotation_view_self` | Preview | Allows user to view their own annotations only | | `base_explorer` | Explorer | Allows access to content in the folder tree based on user/file/token permissions | | `base_picker` | Picker | Allows access to content in the folder tree based on user/file/token permissions | | `base_preview` | Preview | Allows the user to preview the file, nothing else | | `base_sidebar` | Sidebar | Allows the user to get basic file info needed for the sidebar UI element | | `base_upload` | Uploader | Allows upload into the folder specified under `resource` when downscoping the token | | `item_delete` | Explorer | Allows files and folders to be deleted | | `item_download` | Explorer, Preview | Allows files or a folder's content to be downloaded | | `item_preview` | Explorer | Enables preview of a file | | `item_rename` | Explorer | Allows files and folders to be renamed | | `item_share` | Explorer, Picker | Allows the item specified under `resource` of the token exchange to be shared | | `item_upload` | Picker | Allows upload in the content picker | The standard OAuth scopes are also supported when downscoping. | OAuth Scope | Description | | ------------------------------ | ---------------------------- | | `ai.readwrite` | Manage AI API | | `manage_managed_users` | Manage managed users | | `manage_app_users` | Manage app users | | `manage_data_retention` | Manage retention policies | | `manage_enterprise_properties` | Manage enterprise properties | | `manage_groups` | Manage groups | | `manage_webhook` | Manage webhooks | | `sign_requests.readwrite` | Manage sign requests | [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 version. 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 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/files/12345 \ -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 `field` 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 `field=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` | An 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 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 notable the creation is 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 requests 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 a `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 followed `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 a `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 returns 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 it's 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 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 in 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 Select Auth Method for guidance on choosing the method. ## 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 to change 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 Changing between Client Credentials 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 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 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 then 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 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 details on working with Box Embed, see this guide. # Box Sign Source: https://developer.box.com/guides/box-sign/index Programmatically harness the full functionality of the Box Sign web app experience by leveraging Box Sign’s API endpoints to create, list, resend, and cancel sign requests. ## Enablement 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, please see our [support article][restrict]. ## Required scopes The following scopes must be enabled for an application before use of Box Sign's endpoints. * Read all files and folders stored in Box * Write all files and folders stored in Box * Manage signature requests Depending on the selected authentication method and enterprise's settings, your application may require Admin authorization or re-authorization before successful use of any newly selected scopes. ## Events Please see our events guide for more information. ## Webhooks Please see our webhooks guide for more information. ## Rate Limits Please see our rate limit guide for more information. ## Testing Due to the feature parity, it may be useful to familiarize yourself with [Box Sign functionality using the Box web app][webapp] before leveraging the API. As with all API endpoints, we recommend testing via [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 # List Box Sign Requests Source: https://developer.box.com/guides/box-sign/list-sign-requests ## All The get sign requests endpoint can be used 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`); ``` ## By ID The get sign requests by ID endpoint can be used 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 or in the response when creating a Box Sign request. ```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` ); ``` # Resend Box Sign Request Source: https://developer.box.com/guides/box-sign/resend-sign-request The resend a Box sign request endpoint can be used to resend request emails to any remaining signers. A Box Sign request cannot be resent if the status is: `signed`, `cancelled`, `declined`, `expired`, `error_sending`, or `error_converting`. If a Box Sign request was recently sent, you will need to wait 10 minutes before resending. If you try before this time has passed you will receive a 400 error. Reminder emails can be enabled when creating a Box Sign request to avoid the need to resend the request. ```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`); ``` # Create Sign Request with Sign Template Source: https://developer.box.com/guides/box-sign/sign-templates The Sign Request API allows you to use a predefined Box Sign template when creating a sign request. The template includes placeholders that are automatically populated with data when creating the request. ## Create Template Start with creating a Box Sign template that includes `text`, `date`, and `signature` fields you will need for you request. See the [template guides][docuprep] guide for detailed instructions. ## Get the Template ID To send a sign request, you need to pass the ID of the template you want to use. List the templates to find the `template_id`. ```json theme={null} "entries": [ { "id": "6ae28666-03c4-4ac1-80db-06a90d3b1361", "name": "Contract.pdf", "parent_folder": { "id": "157064745449", "etag": "0", "type": "folder", "sequence_id": "0", "name": "My Sign Requests" }, "source_files": [ { "id": "1216382236853", "etag": "0", "type": "file", "sequence_id": "0", "sha1": "ca9c75cda0d5e3c3c9b0a1e6d42cb5e29a211ab6", "file_version": { "id": "1327286673653", "type": "file_version", "sha1": "ca9c75cda0d5e3c3c9b0a1e6d42cb5e29a211ab6" } } ], "signers": [ { "email": "", "label": "reader", "public_id": "4Z8QZZV4", "role": "final_copy_reader", "is_in_person": false, "order": 1, "inputs": [...] }, { "email": "", "label": "signer1", "public_id": "4Z8QZZV4", "role": "signer", "is_in_person": false, "order": 1, "inputs": [...] }, { "email": "", "label": "signer2", "public_id": "13VK8794", "role": "signer", "is_in_person": false, "order": 1, "inputs": [ { "document_tag_id": "signer2_full_name", "id": "da431975-55c5-4629-86ae-3fb12dda1386", "type": "text", "text_value": null, "is_required": true, "content_type": "full_name", ... }, { "document_tag_id": null, "id": "b5a76a22-8d48-456e-a012-22a12fc91eb7", "type": "signature", ... }, { "document_tag_id": null, "id": "7e0cc4ee-b878-4739-afde-acbf69b117b2", "type": "date", "date_value": null, ... } ], } ] ... } ] ``` The response is similar to the following one (abbreviated for guide purposes). For the full response example, see Box Sign template API. You can also learn more about the specific parameters in the Create Sign Request guide. ```json theme={null} "entries": [ { "id": "6ae28666-03c4-4ac1-80db-06a90d3b1361", "name": "Contract.pdf", "parent_folder": { "id": "157064745449", "etag": "0", "type": "folder", "sequence_id": "0", "name": "My Sign Requests" }, "source_files": [ { "id": "1216382236853", "etag": "0", "type": "file", "sequence_id": "0", "sha1": "ca9c75cda0d5e3c3c9b0a1e6d42cb5e29a211ab6", "file_version": { "id": "1327286673653", "type": "file_version", "sha1": "ca9c75cda0d5e3c3c9b0a1e6d42cb5e29a211ab6" } } ], "signers": [ { "email": "", "label": "reader", "public_id": "4Z8QZZV4", "role": "final_copy_reader", "is_in_person": false, "order": 1, "inputs": [...] }, { "email": "", "label": "signer1", "public_id": "4Z8QZZV4", "role": "signer", "is_in_person": false, "order": 1, "inputs": [...] }, { "email": "", "label": "signer2", "public_id": "13VK8794", "role": "signer", "is_in_person": false, "order": 1, "inputs": [ { "document_tag_id": "signer2_full_name", "id": "da431975-55c5-4629-86ae-3fb12dda1386", "type": "text", "text_value": null, "is_required": true, "content_type": "full_name", ... }, { "document_tag_id": null, "id": "b5a76a22-8d48-456e-a012-22a12fc91eb7", "type": "signature", ... }, { "document_tag_id": null, "id": "7e0cc4ee-b878-4739-afde-acbf69b117b2", "type": "date", "date_value": null, ... } ], } ] ... } ] ``` ## Create the sign request Follow these steps to create sign request using a template: 1. In the request body, provide the `template_id`: ```json theme={null} { "template_id": "6ae28666-03c4-4ac1-80db-06a90d3b1361", "parent_folder": { "id": "123456789", "etag": "0", "type": "folder", "sequence_id": "0", "name": "My Sign Requests" }, ... } ``` 2. Add the signer email addresses and roles: ```json theme={null} { "template_id": "6ae28666-03c4-4ac1-80db-06a90d3b1361", "parent_folder": { "id": "157064745449", "etag": "0", "type": "folder", "sequence_id": "0", "name": "My Sign Requests" }, "signers": [ { "email": "signer1@sample.com", "role": "signer" }, { "email": "signer2@sample.com", "role": "signer" } ] } ``` 3. Add the `prefill_tags` to populate the fields. Make sure the signer order is the same as the one displayed on the template. If the template had `signer1` first and then `signer2`, the `POST` request must reflect the same order to assign the proper signers. ```json theme={null} { "template_id": "6ae28666-03c4-4ac1-80db-06a90d3b1361", "parent_folder": { "id": "123456789000", "etag": "0", "type": "folder", "sequence_id": "0", "name": "My Sign Requests" }, "signers": [ { "email": "signer1@sample.com", "role": "signer" }, { "email": "signer2@sample.com", "role": "signer" } ], "prefill_tags": [ { "document_tag_id": "signer1_full_name", "text_value": "Aaron Levie" }, { "document_tag_id": "signer2_full_name", "text_value": "Albert Einstein" } ] } ``` 4. Send the `POST` request. The response will be similar to the following: ```json theme={null} { "is_document_preparation_needed": false, ... "signers": [ { "email": "reader@sample.com", "role": "final_copy_reader", }, { "email": "signer1@sample.com", "role": "signer", }, { "email": "signer2@sample.com", "role": "signer", } ], "id": "d02fefd2-15fa-431f-a127-2b4525616ae6", "prefill_tags": [ { "document_tag_id": "signer1_full_name", "text_value": "Aaron Levie", }, { "document_tag_id": "signer2_full_name", "text_value": "Albert Einstein", } ], "source_files": [], "parent_folder": { "id": "123456789000", "type": "folder", "name": "My Sign Requests" }, "name": "Contract.pdf", "type": "sign-request", "status": "created", "sign_files": { "files": [ { "id": "123456789", "type": "file", "name": "Contract.pdf", } ], "is_ready_for_download": true }, "template_id": "6ae28666-03c4-4ac1-80db-06a90d3b1361" } ``` [docuprep]: https://support.box.com/hc/en-us/articles/4404094944915-Creating-templates [parentfolder]: /guides/box-sign/create-sign-request#parent-folder [signers]: /guides/box-sign/create-sign-request#signers # Suppress default Box Sign notifications Source: https://developer.box.com/guides/box-sign/suppress-sign-notifications Box Sign API allows you to suppress the default Box email notifications sent during the Sign workflow. ​​This feature facilitates the ownership of Box Sign notifications with the following options: * You can use a fully-customized email notification template to send emails from your domain. * Apart from emails, you can send push notifications or text messages.​ When you choose to suppress Box email notifications, your organization assumes responsibility for ensuring the delivery to Signers of all notifications at the appropriate time in the signing process and with the appropriate content, in compliance with all applicable laws and regulations, including with respect to obtaining Signer consent to the delivery methods used, if applicable. ## Using Box Sign API to suppress default notifications To suppress Box Sign email notifications, you must set the following parameters: 1. Set the `suppress_notifications` parameter in the `signers` object to `true` to turn the notifications off. 2. Set the `embed_url_external_user_id` parameter to specify the user who will not receive notifications. This configuration turns off the automatic Box Sign email notifications for a given user. As a result, you can configure and send your own notifications. ```sh theme={null} curl -i -X POST "https://api.box.com/2.0/sign_requests" \ -H "authorization: Bearer " \ -d '{ "signers": [ { "role": "signer", "email": "example_email@box.com" "suppress_notifications": true "embed_url_external_user_id": "1234" } ], "source_files": [ { "type": "file", "id": "123456789" } ], "parent_folder": { "type": "folder", "id": "0987654321" } }' ``` ## Signing Log entries When Box Sign default notifications are suppressed, the Signing Log will indicate that the sender has suppressed all Box Sign notifications. The log will also provide information on the system used for purposes of notification delivery and the user ID of the signer on your organization’s system, as provided to Box Sign through your API integration. # 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 # 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 show 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()) ``` # 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, as 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 files 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 in to 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 a `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 a `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 kind 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 your 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 setup Box in very 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 your 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 principals 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 preform 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 user’s 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 application 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 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 /platform-app-approval [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 a enterprise-level record that represent 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 in 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 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, 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 managed 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 **Manager 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 **Manager 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 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 the \[`POST /external_users/post_external_users_submit_delete_job`]. ```sh cURL theme={null} curl -X -L POST "https://api.box.com/2.0/external_users/external_users_submit_delete_job" \ -H "authorization: Bearer " \ -d '{ "external_users": "type": "array" "description": "List of external users to delete." "items": "$ref: #/components/schemas/UserReference" }' ``` 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 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 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. # 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 Market 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 ``` # Create webhooks (v1) Source: https://developer.box.com/guides/webhooks/v1/create-v1 V1 webhooks are created in the [Developer Console][devconsole] by following the steps below. 1. Navigate to your application in the [Developer Console][devconsole] 2. Select the **Webhooks** tab. 3. Click the **Create a new Webhook** button. 4. Fill in the form, including event triggers, an endpoint URL and one or more callback parameters. 5. Click **Save Webhook**. **Callback parameters** Unlike the V2 Webhooks, these manual webhooks need to be configured with the data you'd like. This data will be sent as a query string either in the body or as a query parameter, for example `name=Contract.pdf&type=file`. ## Developer Mode By default V1 webhooks only work for users that are listed as application collaborators in the **General Settings** tab in the Developer Console. To enable a webhooks for all users, please [contact support][support]. ## Enabling a webhook After creating a webhook, the application must be added to the user's account to begin use. To obtain the URL to add the app, follow the directions below for OAuth 2.0 authentication apps: 1. Navigate to the **Integrations** tab for the application in the [Developer Console][devconsole]. 2. Click **Submit My App**. Do not worry, you will not be completing the submission process! 3. At the bottom of the page, click **Preview**. 4. Click **Add** For all other authentication types, you will need to contact support to obtain this URL. Webhooks will now trigger for any configured events that are occur in the user's account. [devconsole]: https://app.box.com/developers/console [support]: https://support.box.com # 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 # V1 Webhooks Source: https://developer.box.com/guides/webhooks/v1/index Webhooks created using the [Developer Console][console] monitor changes to all files and folders within a user's account. When creating one of these webhooks it is not possible specify a specific object to bind the webhook to. To create a webhook for a specific file or folder, you will need to leverage v2 webhooks. Webhooks created through this process will not show when listing all webhooks for a user via API call. All V1 webhooks are visible in the **Webhooks** tab in the [Developer Console][console]. [devconsole]: https://app.box.com/developers/console [list_webhooks]: /guides/webhooks/v2/list-v2 [console]: https://app.box.com/developers/console # 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 ## Flow
Webhook flow
When an event triggers a webhook for a file or a folder, it makes a 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 will change, 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, as well as the event that has been 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 will not change, while the ID in the header changes between calls. | | `created_at` | The time/date when an event was triggered at. | | `trigger` | The name of the action that triggered an event, for example `FILE.UPLOADED`. | | `webhook` | The webhook ID for which an event triggered. | | `created_by` | The user that 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 a HTTP status code in the `200` to `299` range within 30 seconds of sending the payload. Box will retry webhook deliveries up to 12 times over a period of 2 hours. 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 an `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. # Signature Verification Source: https://developer.box.com/guides/webhooks/v2/signatures-v2 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 attacks. 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 signatures 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 # Working with Box Sign Source: https://developer.box.com/sign/index Working with box sign image This learning page provides developers with practical insights into working with [Box Sign][sign], aiming to facilitate the integration of the Box Sign engine into their applications. ## Quick start Use the [Quick start][quick-start] to go straight into the creation of a signature request. ## Technical use cases In the [Technical use cases][technical-use-cases], you will learn how to handle the different types of documents that can be used in a signature request: from unstructured documents that require a preparation step, through templates, to generated ready to sign documents. ## Request Options In the [Request options][request-options], you will find a detailed exploration of the available customization and configuration options when sending signing requests through the Box Sign API. Learn how to tailor the signing experience to match your application's user interface, workflow, and specific requirements. Let's get started! [sign]: https://www.box.com/esignature [quick-start]: /sign/quick-start [request-options]: /sign/request-options [technical-use-cases]: /sign/technical-use-cases # API Basics Source: https://developer.box.com/sign/quick-start/api-basics ## Sign API The Sign request endpoint is used to create and manage signature requests. You can create, resend, and cancel signature requests. You can also list all signature requests and get details of a specific signature request. The endpoint is `https://{api.box.com}/2.0/sign_requests`. The following table lists the operations that you can perform on this endpoint. | Operation | Endpoint | Description | | --------- | --------------------------- | -------------------------------------------- | | GET | `/sign_requests` | List all signature requests. | | GET | `/sign_requests/:id` | Get details of a specific signature request. | | POST | `/sign_requests` | Create a signature request. | | POST | `/sign_requests/:id/resend` | Resend a signature request. | | POST | `/sign_requests/:id/cancel` | Cancel a signature request. | For full details on the request and response parameters, see the [Sign request API reference][sign-api-reference]. ## Sign templates API The Sign templates endpoint is used to list and get details of a template. You can not create, edit, or delete templates using the API. These templates are exclusively managed in the Box web application. The endpoint is `https://{api.box.com}/2.0/sign_templates`. The following table lists the operations that you can perform on this endpoint. | Operation | Endpoint | Description | | --------- | --------------------- | ----------------------------------- | | GET | `/sign_templates` | List all templates. | | GET | `/sign_templates/:id` | Get details of a specific template. | For a full details on the request and response parameters, see the [Sign template request API reference][sign-api-template-ref]. [sign-api-reference]: /reference/resources/sign-request [sign-api-template-ref]: /reference/resources/sign-template # Quick start Source: https://developer.box.com/sign/quick-start/index Get a sense of how the [Box Sign API][api-basics] is structured and how to create your first signature request. The Sign API does not follow the traditional CRUD model. You can create, resend, and cancel signature requests. You can also list all signature requests and get details of a specific signature request. Sign Templates API is read-only. You can list all templates and get details of a specific template. Once you get a sense of the API, you can create [your first signature request][quick-start]. [api-basics]: /sign/quick-start/api-basics [quick-start]: /sign/quick-start/your-first-request # Your first request Source: https://developer.box.com/sign/quick-start/your-first-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 You can use the Box Sign API or one of the available 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 Gen SDK theme={null} def sign_doc_single( client: Client, document_id: str, destination_folder_id: str, signer_email: str, prep_needed: bool = False, ) -> SignRequest: # Sign request params source_file = FileBase(id=document_id, type=FileBaseTypeField.FILE) destination_folder = FolderMini( id=destination_folder_id, type=FolderBaseTypeField.FOLDER ) signer = SignRequestCreateSigner(signer_email) # sign document sign_request = client.sign_requests.create_sign_request( signers=[signer], parent_folder=destination_folder, source_files=[source_file], is_document_preparation_needed=prep_needed, ) return sign_request def main(): conf = ConfigOAuth() client = get_client_oauth(conf) # Simple sign a pdf request with preparation sign_pdf_prep = sign_doc_single( client, SIMPLE_PDF, SIGN_DOCS_FOLDER, SIGNER_A, True ) if sign_pdf_prep.prepare_url is not None: open_browser(sign_pdf_prep.prepare_url) ``` This will result 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 Python Gen SDK 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 should check the status of the request before proceeding, and handle any errors. A signature request can have the following statuses: Signature flow * `converting`: The file is converted to a `.pdf` for the signing process once the sign request is sent. * `error_converting`: An issue occurred while converting the file to a `.pdf`. * `created`: When the `document_preparation_is_needed` is set to `true`, but the `prepare_url` has not yet been visited. * `sent`: The request was successfully sent, but no signer has interacted with it. * `error_sending`: An issue occurred while sending the request. * `viewed`: The first, or only, signer clicked on **Review document** in the signing email or visited the signing URL. * `downloaded`: The document was downloaded by the signer. * `signed`: All signers completed the request. * `signed and downloaded`: The document was signed and downloaded by the signer. * `declined`: If any signer declines the request. * `cancelled`: If the request is cancelled via UI or API. * `expired`: The date of expiration has passed with outstanding, incomplete signatures. * `finalizing`: All signers have signed the request, but the final document with signatures and the signing log have not been generated yet. * `error_finalizing`: The `finalizing` phase did not complete successfully. ## Preparing the document Depending on your technical use case you may need to prepare the document. In this specific 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, then your application should open 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 the [technical use cases][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 click 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 represents the basic use case for Box Sign. The `create` method has many options that you can use to customize your signature request. Be sure to check the [request options][request-options], and the [technical use cases][technical-use-cases] sections for more information. [request-options]: /sign/request-options [technical-use-cases]: /sign/technical-use-cases # 21 CFR Part 11 requests Source: https://developer.box.com/sign/request-options/cfr-part-11 [21 CFR Part 11][cfr] is a US Food and Drug Administration (FDA) regulation that defines the criteria for accepting electronic records and electronic signatures as equivalent to paper records and handwritten signatures. Organizations in regulated industries, such as life sciences, refer to these requirements as GxP (good practice) compliance. You can create and manage both 21 CFR Part 11 and standard signature requests through the Box Sign API. You can now require 21 CFR Part 11 signatures in your own applications, as well as in Box Automate workflows and the Box for Salesforce integration, rather than only in the Box web app. Support for 21 CFR Part 11 in Box Sign requires GxP Validation. In addition to GxP Validation, customers must enable 21 CFR Part 11 for specific users and groups within the Admin Console before they can create `cfr11` requests through the API. To learn more, see [21 CFR Part 11 compliance support][cfr]. ## Choose a request flow The `request_flow` field determines whether a request follows the 21 CFR Part 11 or the standard flow. It accepts the following values: | Value | Description | | ---------- | ---------------------------------------------------------------------------------- | | `cfr11` | The request follows the 21 CFR Part 11 flow and enforces the related requirements. | | `standard` | The request follows the standard Box Sign flow. | The `request_flow` field is optional when you create a request. If you don't set it, Box selects a default based on your enterprise's admin setting. If you set a value that your account doesn't have access to, the API returns a `403 Forbidden` error. The `request_flow` field is also returned in the response when you create, retrieve, or list signature requests and templates, so you can identify which flow each request or template uses. ## Requirements for 21 CFR Part 11 requests When `request_flow` is `cfr11`, the following requirements apply: * **Signer login is required.** The `login_required` field on each signer is always `true`. If you set `login_required` to `false`, the API returns a `400 Bad Request` error. * **Signature color can't be red.** If you set `signature_color` to `red`, the API returns a `400 Bad Request` error. Use `blue` or `black` instead. * **Each recipient must have valid fields.** Each recipient or recipient group must be assigned either no fields, or at least one required signature or initials field. For example, you can't create a 21 CFR Part 11 request that has only text fields, but you can create one with no placeholders at all. ## Create a 21 CFR Part 11 request Set `request_flow` to `cfr11` when you create a signature request. How Box validates the request depends on whether you provide a template, source files, or both. ### Create from a template When you create a request from a template, pass the `template_id` and set `request_flow` to `cfr11`. Box validates the template's placeholders at request time against the [21 CFR Part 11 requirements](#requirements-for-21-cfr-part-11-requests). ```sh cURL theme={null} curl -i -X POST "https://api.box.com/2.0/sign_requests" \ -H "authorization: Bearer " \ -H "content-type: application/json" \ -d '{ "request_flow": "cfr11", "template_id": "123075213-af2c8822-3ef2-4952-8557-52d69c2fe9cb", "signers": [ { "role": "signer", "email": "signer@example.com" } ], "parent_folder": { "type": "folder", "id": "234102987614" } }' ``` If the template contains only non-signature fields, the request fails with a `400 Bad Request` error. See [Error responses](#error-responses). ### Create from source files When you create a request from `source_files`, you define signature, initials, and other placeholders directly in the document using [template tags][tags]. Box extracts these placeholders asynchronously while converting the document, so it can't validate them at request time. For this reason, the `is_document_preparation_needed` field becomes **mandatory** for 21 CFR Part 11 requests created from source files. The value you set determines when validation runs: | `is_document_preparation_needed` | When validation runs | | -------------------------------- | ------------------------------------------------------------------------------------------ | | `true` | When you review the document in the Box Sign preparation page, before the request is sent. | | `false` | Automatically, during the asynchronous document conversion after the request is sent. | #### Review the document before sending When you set `is_document_preparation_needed` to `true`, the response includes a `prepare_url`. Open this URL in a browser to review the document in the Box Sign preparation page before the request is sent. During preparation, Box: * Validates signature and initials placeholders. * Automatically adjusts those placeholders to meet 21 CFR Part 11 requirements. * Displays a warning when any placeholder is resized, so you can review the changes before sending. From the preparation page, you can accept the adjustments and send the request, or cancel it. ```sh cURL theme={null} curl -i -X POST "https://api.box.com/2.0/sign_requests" \ -H "authorization: Bearer " \ -H "content-type: application/json" \ -d '{ "request_flow": "cfr11", "is_document_preparation_needed": true, "source_files": [ { "type": "file", "id": "1358047520478" } ], "signers": [ { "role": "signer", "email": "signer@example.com" } ], "parent_folder": { "type": "folder", "id": "234102987614" } }' ``` #### Validate during conversion When you set `is_document_preparation_needed` to `false`, Box validates the placeholders during the asynchronous document conversion. If validation fails: * The request moves to the `error` state, with the `error_code` field set to `cfr11_validation_failed`. * Box emails the requester to report the failure and points them to the API documentation. * You need to revise the file to meet 21 CFR Part 11 requirements and submit a new request. ### Create from a template and source files You can pass both a `template_id` and `source_files`. In this case, the source files overwrite the template's document, while other information from the template, such as signers, stays linked to the request. Box validates the request the same way as when you [create from source files](#create-from-source-files), so `is_document_preparation_needed` is mandatory. ## Read CFR Part 11 requests and templates When you retrieve or list signature requests and templates, the response includes fields that describe the 21 CFR Part 11 flow and signing details. | Field | Type | Description | | --------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `request_flow` | String | The flow the request or template uses: `cfr11` or `standard`. | | `signers[].login_required` | Boolean | Always `true` when `request_flow` is `cfr11`. | | `signers[].inputs[].reason` | String, nullable | The signing reason captured for the input. Populated only for applicable inputs, such as signature or initials fields in a `cfr11` request. `null` otherwise. | | `signers[].inputs[].is_validated` | Boolean, nullable | Whether the signer re-authenticated for the input. `true` after a successful re-authentication, `false` if the signer filled the field but hasn't re-authenticated yet, and `null` for standard requests or inputs where it doesn't apply. | The following example shows a retrieved 21 CFR Part 11 request: ```json theme={null} { "request_flow": "cfr11", "signers": [ { "login_required": true, "inputs": [ { "text_value": "John Doe", "type": "signature", "content_type": "signature", "page_index": 0, "read_only": false, "reason": "I read and approve this document", "is_validated": true } ] } ] } ``` You can retrieve, list, cancel, and resend a 21 CFR Part 11 request or template as long as you have access to it, even if 21 CFR Part 11 isn't enabled for your account. Box verifies permission to use a template when you create a request from it, not when you retrieve the template. ## Error responses 21 CFR Part 11 requests return the following errors synchronously when validation fails at request time: | Scenario | Status | Reason | | ------------------------------------------------------------- | ------ | --------------------- | | `login_required` is set to `false` for a `cfr11` request. | `400` | `invalid_parameter` | | `signature_color` is set to `red` for a `cfr11` request. | `400` | `invalid_parameter` | | A `cfr11` request contains only non-signature fields. | `400` | `invalid_template` | | The specified `template_id` doesn't exist. | `404` | `not_found` | | You don't have permission to use the specified `template_id`. | `404` | `not_found` | | You don't have access to the specified `request_flow`. | `403` | `forbidden_by_policy` | For example, setting `login_required` to `false` returns: ```json theme={null} { "type": "error", "code": "bad_request", "status": 400, "message": "Bad request", "request_id": "122afdc1863c8713a175148aa614578ff", "context_info": { "errors": [ { "name": "login_required", "message": "The 'login_required' field cannot be set to false when the request flow is 'cfr11'. Please ensure that 'login_required' is true for 'cfr11' requests.", "reason": "invalid_parameter" } ] } } ``` Placeholder errors for requests created from source files using [template tags][tags] aren't returned at request time, because Box extracts placeholders asynchronously during document conversion. When this validation fails, the request moves to the `error` state with the `error_code` field set to `cfr11_validation_failed`. For more information, see [Validate during conversion](#validate-during-conversion). ## Backward compatibility Existing requests and templates that were created before 21 CFR Part 11 support was added to the API are classified automatically. For these items, Box determines the `request_flow` value based on the user's permissions, so older requests and templates return a valid `request_flow` when you retrieve them. [cfr]: https://docs.box.com/en/box-sign/sending-a-document-for-signature/21-cfr-part-11-compliance-support [tags]: https://docs.box.com/en/box-sign/templates/creating-templates-using-tags # Custom email and notifications Source: https://developer.box.com/sign/request-options/custom-email ## Email subject and body By default, the email sent to the signers contains a link to the document, a generic subject, and a generic message. If you are using templates managed within Box, the subject and message body can be set in the template itself. However, if you are not using templates, you can still customize the email messages sent to the signers by passing the `email_subject` and the `email_message` parameters. Both parameters accept strings, but the `email_message` parameter also accepts HTML with some limitations. Only some HTML tags are allowed. Links included in the message are also converted to hyperlinks in the email. The message parameter may contain the following HTML tags: * `a`, `abbr`, `acronym`, `b`, `blockquote`, `code`, `em`, `i`, `ul`, `li`, `ol`, `strong` Custom styles on these tags are not allowed. Be aware that when the text to HTML ratio is too high, the email may end up in spam filters or clipped. For example: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ej...3t' \ --data-raw '{ "email_subject": "All we need is your signature to get started", "parent_folder": { "id": "234102987614", "type": "folder" }, "source_files": [ { "id": "1358047520478", "type": "file" } ], "signers": [ { "email": "signer@example.com", "role": "signer" } ] }' ``` ```python Python Gen SDK theme={null} def sign_doc_single_more_options( client: Client, ... email_subject: str = None, email_message: str = None, ) -> SignRequest: ... # sign document sign_request = client.sign_requests.create_sign_request( ... email_subject=email_subject, email_message=email_message, ) return sign_request def main(): ... # Sign with custom email subject sign_custom_email_subject = sign_doc_single_more_options( client, SIMPLE_PDF, SIGN_DOCS_FOLDER, SIGNER_A, prep_needed=False, email_subject="All we need is your signature to get started", ) ``` ## Manual notification By now, you've noticed that the signature request sends an email notification to the signers by default. This email is sent from a `box.com` domain and email system. You can take over the notification process by setting the `embed_url_external_user_id` parameter to an identifier of your choice for a specific signer. By setting this parameter, the signer will not receive an email notification, and within the signature request, you get back both an `embed_url` and an `iframeable_embed_url`. The `embed_url` can be opened directly, so it is suitable for your app to send it in an email, or by any other notifications system for the signer to open. The `iframeable_embed_url` is suited to be used with the [Box Embedded Sign Client][embed], which allows you to embed the Box Sign client on an iframe within your web app. For example see this request: ```bash theme={null} --header 'Content-Type: application/json' \ --header 'Authorization: Bearer fN...dD' \ --data-raw '{ "is_document_preparation_needed": false, "parent_folder": { "id": "234102987614", "type": "folder" }, "source_files": [ { "id": "1355143830404", "type": "file" } ], "signers": [ { "email": "signer@example.com", "embed_url_external_user_id": "1234", "role": "signer" } ] }' ``` ```python Python Gen SDK theme={null} def sign_doc_embed_url( client: Client, document_id: str, destination_folder_id: str, signer_email: str, signer_embed_url_id: str, ) -> SignRequest: # Sign request params source_file = FileBase(id=document_id, type=FileBaseTypeField.FILE) destination_folder = FolderMini( id=destination_folder_id, type=FolderBaseTypeField.FOLDER ) signer = SignRequestCreateSigner( email=signer_email, embed_url_external_user_id=signer_embed_url_id, ) # sign document sign_request = client.sign_requests.create_sign_request( signers=[signer], parent_folder=destination_folder, source_files=[source_file], ) return sign_request def main(): """Simple script to demonstrate how to use the Box SDK""" conf = ConfigOAuth() client = get_client_oauth(conf) # Sign with phone verification sign_with_embed_url = sign_doc_embed_url( client, SIMPLE_PDF, SIGN_DOCS_FOLDER, SIGNER_A, SIGNER_A_EXTERNAL_ID, ) check_sign_request(sign_with_embed_url) ``` Returns (simplified): ```json theme={null} { "is_document_preparation_needed": false, "signers": [ { "email": "sender@example.com", "role": "final_copy_reader", }, { "email": "signer@example.com", "role": "signer", "embed_url_external_user_id": "1234", "embed_url": "https://app.box.com/sign/document/22a990ce-4e24-463b-b2f4-124820fe161a/9331fe9ac85650d61645d4b0fd30fe3e0ebee7921720ab6ecca587654d3cd875/", "iframeable_embed_url": "https://app.box.com/embed/sign/document/22a990ce-4e24-463b-b2f4-124820fe161a/9331fe9ac85650d61645d4b0fd30fe3e0ebee7921720ab6ecca587654d3cd875/" } ], "id": "22a990ce-4e24-463b-b2f4-124820fe161a", } ``` ```yaml theme={null} Simple sign request: 22a990ce-4e24-463b-b2f4-124820fe161a-defddc79c946 Status: created Signers: 2 final_copy_reader: sender@example.com signer: signer@example.com embed_url: https://app.box.com/sign/document/... iframeable_embed_url: https://app.box.com/embed/sign/document/... Prepare url: None ``` You can now take the embedded URLs and use your own notification process or embed the signature client within your own app. [embed]: /guides/box-sign/embedded-sign-client # Redirect URLs Source: https://developer.box.com/sign/request-options/custom-urls Often after signing a document your company might want to redirect the user to a specific web page like a thank you or an onboarding page. There are two features to support these requirements. When the signer completes the signature process, they can be redirected to a web page. The same can happen when the signer declines the signature request. We can customize these pages by passing the `redirect_url` and `decline_redirect_url` parameters. Custom redirect pages For example: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ej...3t' \ --data-raw '{ "is_document_preparation_needed": true, "redirect_url": "https://community.box.com/", "declined_redirect_url": "/", "parent_folder": { "id": "234102987614", "type": "folder" }, "source_files": [ { "id": "1358047520478", "type": "file" } ], "signers": [ { "email": "signer@example.com", "role": "signer" } ] }' ``` ```python Python Gen SDK theme={null} def sign_doc_single_more_options( ... redirect_url: str = None, declined_redirect_url: str = None, ) -> SignRequest: ... # sign document sign_request = client.sign_requests.create_sign_request( ... redirect_url=redirect_url, declined_redirect_url=declined_redirect_url, ) return sign_request def main(): ... # Sign with redirects sign_with_redirects = sign_doc_single_more_options( client, SIMPLE_PDF, SIGN_DOCS_FOLDER, SIGNER_A, prep_needed=False, redirect_url="https://community.box.com/", declined_redirect_url="/", ) check_sign_request(sign_with_redirects) ``` If you sign you’ll be redirected to our forum page. If you decline you’ll be redirected to our developer page. # Extra security (2FA) Source: https://developer.box.com/sign/request-options/extra-security Box Sign enables senders to provide an [additional layer of security][2FA] for their signature requests and reusable templates. You can require signers to verify their identity through CAC/PIV smart card authentication, SMS multifactor authentication, or Box account login. A password can be added alongside any of these verification methods for an extra layer of protection. 2FA Signature request You can add the additional layer of security in a template or when you create a signature request. ## Verification method (recommended) The `verification_method` property on the `signer` object provides a unified way to configure signer verification when you create a sign request. This approach is recommended for new integrations because it uses a consistent structure across all verification types. Set the `type` field to specify which method to use. Supported types: | Type | Description | | --------- | ----------------------------------------------------------------------------------- | | `cac_piv` | Requires the signer to verify with a CAC/PIV smart card. | | `phone` | Requires the signer to complete SMS verification. Include the `phone_number` field. | | `login` | Requires the signer to log in to their Box account before signing. | When you query a sign request or sign template, the `verification_method` property on each signer shows which verification method is set. A `null` value means the signer doesn't need to complete verification. If you set both `verification_method` and a legacy verification property (such as `login_required` or `verification_phone_number`) on the same signer, `verification_method` takes precedence. The API ignores the legacy property. ### CAC/PIV smart card verification To require a signer to verify their identity with a CAC/PIV smart card, set `verification_method` on the signer to `{ "type": "cac_piv" }`. Your enterprise must have CAC/PIV permissions enabled to use smart card authentication. If the enterprise does not have the required permissions, the API returns a `403 Forbidden` error. Please contact your account team to learn more about the CAC/PIV smart card authentication. If the sign request has multiple signers, you must provide a signing order when using `cac_piv` as the verification method. For a single signer, signing order is not required. ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ej...3t' \ --data-raw '{ "parent_folder": { "id": "234102987614", "type": "folder" }, "source_files": [ { "id": "1358047520478", "type": "file" } ], "signers": [ { "email": "signer@example.com", "role": "signer", "verification_method": { "type": "cac_piv" } } ] }' ``` ### SMS verification To require SMS verification, set `verification_method` on the signer to `{ "type": "phone", "phone_number": "" }`. The phone number must include a country code, prefixed with either `+` or `00` (for example, `+15551232190` or `0015551232190`). ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ej...3t' \ --data-raw '{ "parent_folder": { "id": "234102987614", "type": "folder" }, "source_files": [ { "id": "1358047520478", "type": "file" } ], "signers": [ { "email": "signer@example.com", "role": "signer", "verification_method": { "type": "phone", "phone_number": "+15551232190" } } ] }' ``` ### Login verification To require the signer to log in to their Box account before signing, set `verification_method` on the signer to `{ "type": "login" }`. This is the recommended alternative to the `login_required` parameter. ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ej...3t' \ --data-raw '{ "parent_folder": { "id": "234102987614", "type": "folder" }, "source_files": [ { "id": "1358047520478", "type": "file" } ], "signers": [ { "email": "signer@example.com", "role": "signer", "verification_method": { "type": "login" } } ] }' ``` ## Password verification You can require the signer to use a password to open the signature request by passing the `password` parameter in the `signer` object. For example: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ej...3t' \ --data-raw '{ "is_document_preparation_needed": true, "parent_folder": { "id": "234102987614", "type": "folder" }, "source_files": [ { "id": "1358047520478", "type": "file" } ], "signers": [ { "email": "verify@example.com", "role": "signer", "password": "1234" } ] }' ``` ```python Python Gen SDK theme={null} def sign_doc_verify_password( client: Client, document_id: str, destination_folder_id: str, signer_email: str, signer_password: str, ) -> SignRequest: # Sign request params source_file = FileBase(id=document_id, type=FileBaseTypeField.FILE) destination_folder = FolderMini( id=destination_folder_id, type=FolderBaseTypeField.FOLDER ) # signer signer = SignRequestCreateSigner( email=signer_email, password=signer_password, ) # sign document sign_request = client.sign_requests.create_sign_request( signers=[signer], parent_folder=destination_folder, source_files=[source_file], ) return sign_request def main(): ... # Sign with phone verification sign_with_password_verification = sign_doc_verify_password( client, SIMPLE_PDF, SIGN_DOCS_FOLDER, SIGNER_A, "1234", ) ``` Once the signer opens the signature request they should see something like this: Password verification pop-up As the password verification is done on the first step, it prevents the signer from accessing the document until the correct password is provided. ## SMS verification (legacy) You can also configure SMS verification using the `verification_phone_number` parameter on the signer object. This approach is fully supported, but for new integrations we recommend using `verification_method` instead for a consistent experience across all verification types. To require the signer to complete SMS verification, pass the `verification_phone_number` parameter along with their phone number. For example: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ej...3t' \ --data-raw '{ "is_document_preparation_needed": true, "is_phone_verification_required_to_view": true, "parent_folder": { "id": "234102987614", "type": "folder" }, "source_files": [ { "id": "1358047520478", "type": "file" } ], "signers": [ { "email": "verify@example.com", "role": "signer", "verification_phone_number": "+15551232190" } ] }' ``` ```python Python Gen SDK theme={null} def sign_doc_verify_phone( client: Client, document_id: str, destination_folder_id: str, signer_email: str, signer_phone: str, ) -> SignRequest: # Sign request params source_file = FileBase(id=document_id, type=FileBaseTypeField.FILE) destination_folder = FolderMini( id=destination_folder_id, type=FolderBaseTypeField.FOLDER ) signer = SignRequestCreateSigner( email=signer_email, verification_phone_number=signer_phone, ) # sign document sign_request = client.sign_requests.create_sign_request( signers=[signer], parent_folder=destination_folder, source_files=[source_file], is_phone_verification_required_to_view=True, ) return sign_request def main(): ... # Sign with phone verification sign_with_phone_verification = sign_doc_verify_phone( client, SIMPLE_PDF, SIGN_DOCS_FOLDER, SIGNER_A, SIGNER_A_PHONE, ) check_sign_request(sign_with_phone_verification) ``` When the signer tries to access the signature request an SMS verification dialog pops up: Phone verification Then the signer is prompted to enter the code sent in an SMS: Entering the SMS code By default, SMS verification is required at the signing step, which means the signer can view the document before completing the verification. To require SMS verification before the signer can view the document, set the `is_phone_verification_required_to_view` parameter to `true` when creating the sign request. [2FA]: https://support.box.com/hc/en-us/articles/4406861109907-Additional-Signer-Authentication # In person signatures Source: https://developer.box.com/sign/request-options/in-person Imagine your application is used by a salesperson when they are face to face with a customer and an immediate signature is required, for example, to subscribe to a service or to confirm a purchase. In this case, the salesperson can use your application to create a signature request and then hand over the device to the customer to sign the document, immediately closing the deal. Doing this using the Box web application, for example from a template, is very straightforward. You set the signer or signers email so they can receive a copy of the signed document, flag them as in person, and as soon as you send the request, the Sign interface opens requesting the signature for the first signer, then for the second signer, and so on. In order to use this within your application, you need to create a signature request with the `is_in_person` flag set to `true` for each signer. However because your application needs to show the Sign interface to the signer, you also need to use the `embed_url_external_user_id`so that you get back the embedded URLs, and then either open a browser window or use an iframe to display the signature interface. In person signing loops through signers ## Create an in person signature request Let's use a template with a single signer as an example: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer Le...Cb' \ --data-raw '{ "template_id": "ee9a689e-96b6-4076-92a0-b9b765eb09ca", "parent_folder": { "id": "234102987614", "type": "folder" }, "signers": [ { "email": "signer@example.com", "role": "signer", "is_in_person": true, "embed_url_external_user_id": "1234" } ] }' ``` ```python Python Gen SDK theme={null} def sign_doc_in_person( client: Client, document_id: str, destination_folder_id: str, signer_email: str, signer_embed_url_id: str, ) -> SignRequest: # Sign request params source_file = FileBase(id=document_id, type=FileBaseTypeField.FILE) destination_folder = FolderMini( id=destination_folder_id, type=FolderBaseTypeField.FOLDER ) signer = SignRequestCreateSigner( email=signer_email, embed_url_external_user_id=signer_embed_url_id, is_in_person=True, ) # sign document sign_request = client.sign_requests.create_sign_request( signers=[signer], parent_folder=destination_folder, source_files=[source_file], ) return sign_request def main(): """Simple script to demonstrate how to use the Box SDK""" conf = ConfigOAuth() client = get_client_oauth(conf) # Sign with phone verification sign_with_embed_url = sign_doc_embed_url( client, SIMPLE_PDF, SIGN_DOCS_FOLDER, SIGNER_A, SIGNER_A_EXTERNAL_ID, ) check_sign_request(sign_with_embed_url) ``` Resulting in (simplified): ```json theme={null} { "signers": [ { "email": "sender@example.com", "role": "final_copy_reader", "is_in_person": false, }, { "email": "signer@example.com", "role": "signer", "is_in_person": true, "embed_url_external_user_id": "1234", "embed_url": "https://app.box.com/sign/document/...", "iframeable_embed_url": "https://app.box.com/embed/sign/document/..." } ], "id": "a9159d31-d2fb-4e88-9306-02c00de013d1", "parent_folder": { "id": "234102987614", "type": "folder", "name": "signed docs" }, "name": "Simple-PDF (1).pdf", "type": "sign-request", "status": "created", "template_id": "ee9a689e-96b6-4076-92a0-b9b765eb09ca" } ``` ```yaml theme={null} Simple sign request: a9159d31-d2fb-4e88-9306-02c00de013d1 Status: created Signers: 2 final_copy_reader: sender@example.com signer: signer@example.com embed_url: https://app.box.com/sign/document/... iframeable_embed_url: https://app.box.com/embed/sign/document/... Prepare url: None ``` Notice the `embed_url` and `iframeable_embed_url` in the response. Now when we browse to the embed URL, you see the signature interface: In person signing Once finished the signer will receive a copy of the signed document via their email. ## Multiple in person signers As long as the signer is flagged as `is_in_person`, the signing interface cycles through all the signers in the request. For example, if you add a second signer to the request: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer Le...Cb' \ --data-raw '{ "template_id": "ee9a689e-96b6-4076-92a0-b9b765eb09ca", "parent_folder": { "id": "234102987614", "type": "folder" }, "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 } ] }' ``` ```python Python Gen SDK theme={null} def sign_doc_in_person_multiple( client: Client, document_id: str, destination_folder_id: str, signer_a_email: str, signer_a_embed_url_id: str, signer_b_email: str, ) -> SignRequest: # Sign request params source_file = FileBase(id=document_id, type=FileBaseTypeField.FILE) destination_folder = FolderMini( id=destination_folder_id, type=FolderBaseTypeField.FOLDER ) signer_a = SignRequestCreateSigner( email=signer_email, embed_url_external_user_id=signer_embed_url_id, is_in_person=True, ) signer_b = SignRequestCreateSigner( email=signer_email, is_in_person=True, ) # sign document sign_request = client.sign_requests.create_sign_request( signers=[signer_a, signer_b], parent_folder=destination_folder, source_files=[source_file], ) return sign_request def main(): """Simple script to demonstrate how to use the Box SDK""" conf = ConfigOAuth() client = get_client_oauth(conf) # Sign with phone verification sign_with_embed_url = sign_doc_embed_url( client, SIMPLE_PDF, SIGN_DOCS_FOLDER, SIGNER_A, SIGNER_A_EXTERNAL_ID, SIGNER_B ) check_sign_request(sign_with_embed_url) ``` Results in (simplified): ```json theme={null} { "signers": [ { "email": "sender@example.com", "role": "final_copy_reader", "is_in_person": false, }, { "email": "signer_a@example.com", "role": "signer", "is_in_person": true, "embed_url": "https://app.box.com/sign/document/...", "iframeable_embed_url": "https://app.box.com/embed/sign/document/..." }, { "email": "signer_b@example.com", "role": "signer", "is_in_person": true, "embed_url": null, "iframeable_embed_url": null } ], "id": "d066575f-f22b-42fc-b9e2-701468776475", "parent_folder": { "id": "234102987614", "type": "folder", "name": "signed docs" }, "name": "Simple-PDF (3).pdf", "type": "sign-request", "status": "created", "template_id": "ee9a689e-96b6-4076-92a0-b9b765eb09ca" } ``` ```yaml theme={null} Simple sign request: d066575f-f22b-42fc-b9e2-701468776475 Status: created Signers: 3 final_copy_reader: sender@example.com signer: signer_a@example.com embed_url: https://app.box.com/sign/document/... iframeable_embed_url: https://app.box.com/embed/sign/document/... signer: signer_b@example.com Prepare url: None ``` Browsing to the embedded URL shows the signature interface for the first signer: First in person signer Once the first signer has signed, the signature interface automatically switches to the second signer: Alt text # Request options Source: https://developer.box.com/sign/request-options/index The Box Sign API offers a wide range of customization and configuration options when sending signature requests. These options allow developers to tailor the user experience and workflow to match their application's specific requirements. # Multiple signers and roles Source: https://developer.box.com/sign/request-options/multiple-signers ## Multiple signers What if you have a document that needs to be signed by multiple people? This is typical for contracts between two or more entities. Having multiple signers introduces another dimension to the Box Sign process, the order in which the signers need to sign the document. If you do not specify the order, the request is sent to everyone at the same time, and when all parties have signed the document, they each receive a copy with all signatures. If you specify the signing order, the signature request is sent to the first signer. Only when the first signer signs the document, the request is sent to the second signer, and so on. Let’s see this working with an example scholarship contract between a university and a student. In this case the institution/teacher must sign the document first. Creating a method specific for this: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ej...3t' \ --data-raw '{ "is_document_preparation_needed": true, "parent_folder": { "id": "234102987614", "type": "folder" }, "source_files": [ { "id": "1358047520478", "type": "file" } ], "signers": [ { "email": "institution@example.com", "role": "signer", "order": 1 }, { "email": "student@example.com", "role": "signer", "order": 2 }, ] }' ``` ```python Python Gen SDK theme={null} def sign_contract( client: Client, document_id: str, destination_folder_id: str, institution_email: str, student_email: str, prep_needed: bool = False, ) -> SignRequest: """Sign contract""" # Sign request params source_file = FileBase(id=document_id, type=FileBaseTypeField.FILE) destination_folder = FolderMini( id=destination_folder_id, type=FolderBaseTypeField.FOLDER ) # signers institution = SignRequestCreateSigner( email=institution_email, role=SignRequestCreateSignerRoleField.SIGNER, order=1, ) student = SignRequestCreateSigner( email=student_email, role=SignRequestCreateSignerRoleField.SIGNER, order=2, ) # create sign request sign_request = client.sign_requests.create_sign_request( signers=[institution, student], parent_folder=destination_folder, source_files=[source_file], is_document_preparation_needed=prep_needed, ) return sign_request def main(): ... # Multiple signers sign_contract_multi = sign_contract( client, CONTRACT, SIGN_DOCS_FOLDER, institution_email=SIGNER_A, student_email=SIGNER_B, prep_needed=True, ) if sign_contract_multi.prepare_url is not None: open_browser(sign_contract_multi.prepare_url) ``` In this particular example the document needs to be prepared, so the browser to the prepare URL opens. Drag the signature pad, the full name and the date to the appropriate places in the document, and click Send Request: Preparing the contract Notice you now have two signers, with the order already specified. The `color` is also important to identify which signer is which (in this case the institution is blue and the student is green), determining which signature pad, name and date belongs to which signer. If you look at the signature request details, you should see something like this: Signature request details showing the document and signers Indicating that the first request was sent, but the second is waiting for the first to be completed. Go ahead and complete the signature process for both signers. Notice that when you get the second request it is already signed by the first signer. ## Roles So far we have been working with the `signer` role. However there are [other roles][roles] that you can use to customize the signature process. The available roles are, `signer`, `approver`, and `final copy reader` From a developer perspective, this means: * **Signer**: Any person who is allowed to add data to the document. This includes adding a signature, initials, date, but also filling out text fields, check boxes, and radio buttons, even if it does not include a signature. * **Approver**: This role will be asked if they approve the signature request. This approval happens before the preparation step, if enabled, and before the request is sent to any of the signers. This role is useful if you need to get approval from someone before sending the document to the signers. * **Final copy reader**: This role does not interact with the signature process, but will receive a copy of the signed document. Let's use roles to be a bit more creative in the scholarship example. Imagine that the scholarship needs to be approved by the dean, and the legal department receives a final copy of the contract. The flow starts with the signature request, flowed by the dean approval, the institution signature, the student signature, and finally the legal department receives a copy of the signed document: Multiple signers and roles Let's create a method for this: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ej...3t' \ --data-raw '{ "is_document_preparation_needed": true, "parent_folder": { "id": "234102987614", "type": "folder" }, "source_files": [ { "id": "1358047520478", "type": "file" } ], "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" } ] }' ``` ```python Python Gen SDK theme={null} def sign_contract_step( client: Client, document_id: str, destination_folder_id: str, institution_email: str, student_email: str, dean_email: str, legal_email: str, ) -> SignRequest: """Sign contract""" # Sign request params source_file = FileBase(id=document_id, type=FileBaseTypeField.FILE) destination_folder = FolderMini( id=destination_folder_id, type=FolderBaseTypeField.FOLDER ) # signers institution = SignRequestCreateSigner( email=institution_email, role=SignRequestCreateSignerRoleField.SIGNER, order=1, ) student = SignRequestCreateSigner( email=student_email, role=SignRequestCreateSignerRoleField.SIGNER, order=2, ) dean = SignRequestCreateSigner( email=dean_email, role=SignRequestCreateSignerRoleField.APPROVER, ) legal = SignRequestCreateSigner( email=legal_email, role=SignRequestCreateSignerRoleField.FINAL_COPY_READER, ) # create sign request sign_request = client.sign_requests.create_sign_request( signers=[institution, student, dean, legal], parent_folder=destination_folder, source_files=[source_file], is_document_preparation_needed=True, ) return sign_request def main(): ... # Multiple signers and steps sign_contract_multi_step = sign_contract_step( client, CONTRACT, SIGN_DOCS_FOLDER, institution_email=SIGNER_A, student_email=SIGNER_B, dean_email=APPROVER, legal_email=FINAL_COPY, ) if sign_contract_multi_step.prepare_url is not None: open_browser(sign_contract_multi_step.prepare_url) ``` Like before you need to prepare the document, so open the prepare URL in your browser. Notice in the example the institution is represented by blue on the left, and the student by green on the right, and both are signers. Neither the `approver` nor the `final copy reader` can have inputs associated with them. If you do this, their roles will be adjusted to `signer`: Multiple role preparation Continuing the signature process: * The dean approves the scholarship * The institution signs the scholarship * The student signs the scholarship * The legal department receives a copy of the signed document. [roles]: https://support.box.com/hc/en-us/articles/4404105660947-Roles-for-signers # Request expiration Source: https://developer.box.com/sign/request-options/request-expiration There are situations where you might need to [set an expiration date][exp-date] for the signature request. For example, imagine a quote for a service that is valid for 30 days. This proposal has to be signed by a certain date, and if not, the signature request for the quote is no longer valid. All you need to do is pass the `days_valid` parameter. For example: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ej...3t' \ --data-raw '{ "days_valid": 30, "parent_folder": { "id": "234102987614", "type": "folder" }, "source_files": [ { "id": "1358047520478", "type": "file" } ], "signers": [ { "email": "signer@example.com", "role": "signer" } ] }' ``` ```python Python Gen SDK theme={null} def sign_doc_single_more_options( ... days_valid: int = None, ) -> SignRequest: ... # sign document sign_request = client.sign_requests.create_sign_request( ... days_valid=days_valid, ) return sign_request ``` [exp-date]: https://support.box.com/hc/en-us/articles/4404105810195-Sending-a-document-for-signature#:~:text=Step%205%3A%20Setting%20an%20expiration # Resend requests Source: https://developer.box.com/sign/request-options/resend-requests What if the signer did not receive the email, the email was lost, or the signer deleted the email by mistake? You can resend the signature request email to the `signer` , either manually or you can turn on the automatic resend option. ## Manual resend To manually resend the signature request email to the signer, call the `resend_sign_request` method on the `sign_requests` object. You can only do it once every 10 minutes. Here is an example: ```sh cURL theme={null} curl --location --request POST 'https://api.box.com/2.0/sign_requests/ 52f6f86c-c0b3-401e-a4ec-1709f277c469/resend' \ --header 'Authorization: Bearer ej...3t' ``` ```python Python Gen SDK theme={null} def sign_send_reminder(client: Client, sign_request_id: str): """Send reminder to signers""" sign_request = client.sign_requests.resend_sign_request(sign_request_id) return sign_request ``` ## Automatic resend The automatic resend option sends a reminder email to signers that have not signed the document yet, after 3, 8, 13, and 18 days. To enable automatic resend set the `are_reminders_enabled` parameter to `true`. For 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 '{ "are_reminders_enabled": true, "parent_folder": { "id": "234102987614", "type": "folder" }, "source_files": [ { "id": "1355143830404", "type": "file" } ], "signers": [ { "email": "signer@example.com", "role": "signer" } ] }' ``` ```python Python Gen SDK theme={null} def sign_doc_single_more_options( client: Client, document_id: str, destination_folder_id: str, signer_email: str, prep_needed: bool = False, auto_reminder: bool = False, ) -> SignRequest: """Single doc sign by single signer""" # Sign request params source_file = FileBase(id=document_id, type=FileBaseTypeField.FILE) destination_folder = FolderMini( id=destination_folder_id, type=FolderBaseTypeField.FOLDER ) # signer signer = SignRequestCreateSigner(signer_email) # sign document sign_request = client.sign_requests.create_sign_request( signers=[signer], parent_folder=destination_folder, source_files=[source_file], is_document_preparation_needed=prep_needed, are_reminders_enabled=auto_reminder, ) return sign_request def main(): ... # Sign with redirects sign_with_auto_reminder = sign_doc_single_more_options( client, SIMPLE_PDF, SIGN_DOCS_FOLDER, SIGNER_A, prep_needed=False, auto_reminder = True, ) ``` # Set signer language Source: https://developer.box.com/sign/request-options/signer-language Use the `language` parameter to set the language for individual signers in your signature requests. This ensures that each recipient receives emails and signing pages in the designated language, regardless of their Box account settings or browser configuration. ## Overview By default, Box Sign determines the language for each signer based on: * The signer's Box account language setting (if they're logged in at time of signing) * English (if they're not logged in) In some workflows, especially legal or compliance scenarios, you may need to explicitly enforce display in a particular language. With the `language` parameter, you can explicitly set the language for a signer, ensuring compliance with legal and regulatory requirements. This language applies to: * All email notifications sent to the signer * The signing experience page ## Set the language To set the language for a signer, add the `language` field to the `signer` object in your API request. ```sh cURL theme={null} curl -i -X POST "https://api.box.com/2.0/sign_requests" \ -H "authorization: Bearer " \ -d '{ "signers": [ { "role": "signer", "email": "signer@example.com", "language": "nl" } ], "source_files": [ { "type": "file", "id": "123456789" } ], "parent_folder": { "type": "folder", "id": "0987654321" } }' ``` ### Supported language codes Box Sign supports the language codes defined in the Box API language codes specification. For the complete list of supported languages and their codes, see Language codes. Common language codes include: * en — English * nl — Dutch * fr — French * de — German * es — Spanish * ja — Japanese ### Error handling If you specify an unsupported language code, the API returns a **400 Bad Request** error. Example error response: ```json theme={null} { "type": "error", "status": 400, "code": "invalid_request_parameters", "help_url": "/guides/api-calls/permissions-and-errors/common-errors/", "message": "invalid_request_parameters", "request_id": "abcdef123456" } ``` To fix this error, check that your language code matches one of the supported Language codes. # Technical use cases Source: https://developer.box.com/sign/technical-use-cases/index In your application you will be signing different documents from many sources. How can your application process such documents in order for them to be recognized by the Box Sign service? A signature request can have multiple requirements, or inputs, beyond the traditional signature, such as name, date, and initials. These inputs are called signature properties. The Box Sign service needs to know where to place these inputs in the document, and how to recognize them. The first step is to consider if the document has the necessary information for the Box Sign service to recognize the signature properties. If not, then the [document is unstructured][unstructured-docs], and should be prepared before sending the signature request. This is called document preparation, and is an extra step automatically created by the Box Sign service. There are two other types of documents that already have the necessary information for the Box Sign service to recognize the signature properties. The [sign templates][sign-templates], managed in the Box application, and the [structured documents][sign-structured-docs], which are dynamically generated documents, containing specific tags representing the signature properties. Signing unstructured docs [unstructured-docs]: /sign/technical-use-cases/sign-unstructured-docs [sign-templates]: /sign/technical-use-cases/sign-template [sign-structured-docs]: /sign/technical-use-cases/sign-structured-docs # Signing structured docs Source: https://developer.box.com/sign/technical-use-cases/sign-structured-docs A structured document in the context of Box Sign is a document that includes specific tags that can be recognized by the Box Sign API. These tags are used to place the signature properties associated with a specific signer in the document, such as name, date, and signature. This allows your app to handle a dynamic generated document that is ready to be signed, which has a couple of advantages: * The document can be dynamically generated, and the signature properties can be added to the document before creating the signature request, effectively bypassing the document preparation step. * The document format can be handled outside of Box Sign templates, allowing higher flexibility and integration with external document management systems. ## Anatomy of a structured document Here is an example of a structured document, showing the formatting used to place tags in a Microsoft Word document: Using tags in a Microsoft Word document In the sample above `[[c|1]]` means a checkbox assigned to signer 1, and `[[s| 1]]` means a signature pad assigned to signer 1. Notice how the signature pad is using font size 48 to reserve space vertically for the signature. The `[[t|1|id:tag_full_name|n:enter your complete name]]` means a name tag assigned to signer 1, with the label `enter your complete name`, and using an id of `tag_full_name`. Check out this [document][support-tags] for a complete description of all the tags available. Setting the tags to the same `color` as the background will make them invisible, but they will still be there. The number in the tags refer to the signer number, not the signing order, so `[[c|1]]` is the checkbox for signer 1, `[[c|2]]` is the checkbox for signer 2, and so on. Tag 0 is reserved for the sender, and always exists. So even if the sender does not need to input any data into the document, the other signers must start with 1. ## Create a signature request from a structured document This is the same as creating a signature request from an unstructured document. At minimum, you will need to specify the document, the receiving folder and the email of the `signer`. Since the structured document already contains the signature properties details and location, you can bypass the document preparation. This is how the flow would look like, from the generated document, create the signature request and finally sign the document: Signing a structured document Consider this method: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer nQ...xY' \ --data-raw '{ "source_files": [ { "type": "file", "id": "1363379762284" } ], "parent_folder": { "id": "234102987614", "type": "folder" }, "signers": [ { "email": "signer@example.com", "role": "signer" } ] }' ``` ```python Python Gen SDK theme={null} def create_sign_request_structured( client: Client, file_id: str, signer_email: str ) -> SignRequest: """Create a sign request with structured data""" # Sign request params source_file = FileBase(id=file_id, type=FileBaseTypeField.FILE) parent_folder = FolderMini( id=SIGN_DOCS_FOLDER, type=FolderBaseTypeField.FOLDER ) signer = SignRequestCreateSigner(signer_email) # Create a sign request sign_request = client.sign_requests.create_sign_request( signers=[signer], parent_folder=parent_folder, source_files=[source_file], ) return sign_request def main(): ... # Create a sign request with structured data sign_request = create_sign_request_structured( client, STRUCTURED_DOC, SIGNER_A ) check_sign_request(sign_request) ``` Resulting in (simplified): ```json cURL theme={null} { "is_document_preparation_needed": false, "signers": [ { "email": "sender@example.com", "role": "final_copy_reader", }, { "email": "signer@example.com", "role": "signer", } ], "id": "28199d6c-4662-471e-8426-4cbba9affcf1", "source_files": [ { "id": "1363379762284", "type": "file", "name": "Box-Dive-Waiver.docx", } ], "parent_folder": { "id": "234102987614", "type": "folder", "name": "signed docs" }, "name": "Box-Dive-Waiver.pdf", "type": "sign-request", "status": "converting", "sign_files": { "files": [ { "id": "1393138856442", "type": "file", "name": "Box-Dive-Waiver.pdf", } ], }, } ``` ```yaml Python Gen SDK theme={null} Simple sign request: 6878e048-e9bd-4fb1-88c6-8e502783e8d0 Status: converting Signers: 2 final_copy_reader: sender@example.com signer: signer@example.com Prepare url: None ``` If you go to the **signer** email inbox, open the email from Box Sign, click the **Review Document** button, you'll see the document with the signature properties in place: Document with the properties in place After completing the process the signed document looks like this: Signed document ## Pre-populate the signature attributes If you have an external id in the document tags you can use it to pre-populate their values. For example, you can use the `tag_full_name` to pre-populate the name of the signer. See this method: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer nQ...xY' \ --data-raw '{ "prefill_tags": [ { "document_tag_id": "tag_full_name", "text_value": "Signer A" } ], "source_files": [ { "type": "file", "id": "1363379762284" } ], "parent_folder": { "id": "234102987614", "type": "folder" }, "signers": [ { "email": "signer@example.com", "role": "signer" } ] }' ``` ```python Python Gen SDK theme={null} def create_sign_request_structured_with_prefill( client: Client, file_id: str, signer_name, signer_email: str ) -> SignRequest: """Create a sign request with structured data""" # Sign request params source_file = FileBase(id=file_id, type=FileBaseTypeField.FILE) parent_folder = FolderMini( id=SIGN_DOCS_FOLDER, type=FolderBaseTypeField.FOLDER ) signer = SignRequestCreateSigner(signer_email) # tags tag_full_name = SignRequestPrefillTag( document_tag_id="tag_full_name", text_value=signer_name, ) # Create a sign request sign_request = client.sign_requests.create_sign_request( signers=[signer], parent_folder=parent_folder, source_files=[source_file], prefill_tags=[tag_full_name], ) return sign_request def main(): ... # Create a sign request with name pre populate sign_request_pre_pop = create_sign_request_structured_with_prefill( client, STRUCTURED_DOC, "Signer A", SIGNER_A ) check_sign_request(sign_request_pre_pop) ``` Resulting in (simplified): ```json cURL theme={null} { "is_document_preparation_needed": false, "redirect_url": null, "declined_redirect_url": null, "are_text_signatures_enabled": true, "signature_color": null, "is_phone_verification_required_to_view": false, "email_subject": null, "email_message": null, "are_reminders_enabled": false, "signers": [ { "email": "sender@example.com", "role": "final_copy_reader", }, { "email": "signer@example.com", "role": "signer", } ], "id": "11ecebc0-a2b2-4c14-a892-3f56333cc4fa", "prefill_tags": [ { "document_tag_id": "tag_full_name", "text_value": "Signer A", } ], "source_files": [ { "id": "1363379762284", "type": "file", "name": "Box-Dive-Waiver.docx", } ], "parent_folder": { "id": "234102987614", "type": "folder", "name": "signed docs" }, "name": "Box-Dive-Waiver (1).pdf", "type": "sign-request", "status": "converting", "sign_files": { "files": [ { "id": "1393142670032", "type": "file", "name": "Box-Dive-Waiver (1).pdf", } ], }, } ``` ```yaml Python Gen SDK theme={null} Simple sign request: 7b86e46c-72ba-4568-a6ff-787077cca007 Status: converting Signers: 2 final_copy_reader: sender@example.com signer: signer@example.com Prepare url: None ``` The document now has the name pre-populated: Document ready for sign with the name pre-populated ## Extract information from a signed document Let's say you want to extract the name of the signer and the other properties from the signed document. This is useful if you need to tie the information from the signature request back into your systems. Let's create a method to extract the information from the signed request: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests/ 11ecebc0-a2b2-4c14-a892-3f56333cc4fa' \ --header 'Authorization: Bearer nQ...xY' ``` ```python Python Gen SDK theme={null} def check_sign_request_by_id(client: Client, sign_request_id: str): """Check sign request by id""" sign_request = client.sign_requests.get_sign_request_by_id(sign_request_id) print(f"\nSimple sign request: {sign_request.id}") print(f" Status: {sign_request.status.value}") print(f" Signers: {len(sign_request.signers)}") for signer in sign_request.signers: print(f" {signer.role.value}: {signer.email}") for input in signer.inputs: content_type = input.content_type value = None if content_type == SignRequestSignerInputTypeField.CHECKBOX: value = input.checkbox_value elif content_type == SignRequestSignerInputTypeField.TEXT: value = input.text_value elif content_type == SignRequestSignerInputTypeField.DATE: value = input.date_value print( f" {input.type.value}: {value if value is not None else ''}" ) print(f" Prepare url: {sign_request.prepare_url}") def main(): ... # Latest sign request LATEST_SIGN_REQUEST = "7b86e46c-72ba-4568-a6ff-787077cca007" check_sign_request_by_id(client, LATEST_SIGN_REQUEST) ``` Resulting in (simplified): ```json cURL theme={null} { "signers": [ { "email": "sender@example.com", "role": "final_copy_reader", }, { "email": "signer@example.com", "role": "signer", "signer_decision": { "type": "signed", "finalized_at": "2023-12-19T14:53:10.547Z", }, "inputs": [ { "document_tag_id": null, "checkbox_value": true, "type": "checkbox", "content_type": "checkbox", }, { "document_tag_id": "tag_full_name", "text_value": "Signer A", "type": "text", "content_type": "text", }, { "document_tag_id": null, "text_value": "Dec 19, 2023", "date_value": "2023-12-19", "type": "date", "content_type": "date", }, { "document_tag_id": null, "type": "signature", "content_type": "signature", } ], } ], "id": "11ecebc0-a2b2-4c14-a892-3f56333cc4fa", "prefill_tags": [ { "document_tag_id": "tag_full_name", "text_value": "Signer A", } ], "source_files": [ { "id": "1363379762284", "type": "file", "name": "Box-Dive-Waiver.docx", } ], "parent_folder": { "id": "234102987614", "type": "folder", "name": "signed docs" }, "name": "Box-Dive-Waiver (1).pdf", "type": "sign-request", "signing_log": { "id": "1393140642252", "type": "file", "name": "Box-Dive-Waiver (1) Signing Log.pdf", }, "status": "signed", "sign_files": { "files": [ { "id": "1393142670032", "type": "file", "name": "Box-Dive-Waiver (1).pdf", } ], }, } ``` ```yaml Python Gen SDK theme={null} Simple sign request: 7b86e46c-72ba-4568-a6ff-787077cca007 Status: signed Signers: 2 final_copy_reader: sender@example.com signer: signer@example.com checkbox: True text: Rui Barbosa date: 2023-11-15 signature: Prepare url: None ``` ## Summary Structured documents are a great way to integrate with external document management systems, creating dynamic documents that are ready for signature. If your document signature requirements have a lot of options, you can pre-populate these from another data source and save the user's time, but remember that the user who owns these properties can always change them. After the document is signed you can extract the information from the signature request, which is useful if you need to tie it back into your systems. [support-tags]: https://support.box.com/hc/en-us/articles/4404085855251-Creating-templates-using-tags # Signing using templates Source: https://developer.box.com/sign/technical-use-cases/sign-template A [Box Sign template][template] is a specific type of document that not only contains the text, but also the signature requirements and placement. It is prepared for signing in advance, and as such can be sent directly to the signer or signers. Required fields include, for example, the signature pad field, the full name, and the date. These fields have an owner, meaning they are populated by a specific signer and cannot be shared between them. They can be `mandatory` or `optional` , and be pre-populated by your application. However even if pre-populated, they can always be changed by the `signer`. Within the Box web app, the template not only sets the signature fields, but also the number of signers, the order in which they sign, other roles and recipients such as `approver`, and `final_copy_recipient`, email notification settings, and a few more options. For a complete set of options of the signature request please refer to the [request options][request-options] section. These templates are exclusively created and managed in the Box Sign web app, and can be used to create signature requests using the API or the web app. Let's start by creating a template. ## Creating a template From the Box app navigate to the sign menu on the left, then select templates. Navigating to templates under Box Sign Then, click on the New Template button, and choose or upload the document from Box. Selecting a document when creating a template For example, drag and drop a date, a name and a signature pad to the template, like so: Adding the signature, name, and date to the template You can add an [extra layer of security][additional-sec] for a recipient. It works for both a defined recipient with a pre-defined email address, and a placeholder recipient, where the template user has to provide their email address. Save the template. ## Identify the template In order to work with templates in the Box Sign API we are going to need the `template_id` . Consider this method to list all the templates available to the user: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_templates' \ --header 'Authorization: Bearer E9...Q0' ``` ```python Python Gen SDK theme={null} def sign_templates_list(client: Client): """List all sign templates""" sign_templates = client.sign_templates.get_sign_templates() print(f"\nSign templates: {len(sign_templates.entries)}") for sign_template in sign_templates.entries: print(f" {sign_template.id} - {sign_template.name}") def main(): """Simple script to demonstrate how to use the Box SDK""" conf = ConfigOAuth() client = get_client_oauth(conf) user = client.users.get_user_me() print(f"\nHello, I'm {user.name} ({user.login}) [{user.id}]") sign_templates_list(client) ``` Returns something similar to (simplified): ```json cURL theme={null} { "limit": 10, "next_marker": null, "prev_marker": null, "entries": [ { "type": "sign-template", "id": "f2ec720d-47a6-4052-8210-9bfa8d6c349c", "name": "Simple-DOC.pdf", "parent_folder": { "id": "157064745449", "type": "folder", "name": "My Sign Requests" }, "source_files": [ { "id": "1393013714313", "type": "file", } ], "signers": [ { "email": "", "label": "", "role": "final_copy_reader", "inputs": [] }, { "email": "", "label": "Signer", "role": "signer", "inputs": [ { "document_tag_id": null, "id": "d02c8e16-5050-475e-b74b-9a952193e4f8", "type": "date", "date_value": null, "content_type": "date", }, { "document_tag_id": null, "id": "bdcc966e-2ebf-4b3b-aaee-99d4e1161a9e", "type": "text", "text_value": null, "is_required": true, "content_type": "full_name", }, { "document_tag_id": null, "id": "1a8f4cb1-5c09-46bd-96f5-0ab449f19640", "type": "signature", "text_value": null, "is_required": true, "content_type": "signature", } ] } ], } ] } ``` ```yaml Python Gen SDK theme={null} Hello, I'm Rui Barbosa [18622116055] Sign templates: 1 94e3815b-f7f5-4c2c-8a26-e9ba5c486031 - Simple-PDF.pdf ``` ## Creating a signature request from a template The big advantage of using templates is that we do not need to worry about document preparation. Most of the signature options can be set in the template itself. This is how the flow would look like: Signing using a template Using a signature template, create the signature request, and finally sign the document. See this example: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer E9...Q0' \ --data-raw '{ "template_id": "f2ec720d-47a6-4052-8210-9bfa8d6c349c", "parent_folder": { "id": "234102987614", "type": "folder" }, "signers": [ { "email": "signer@example.com", "role": "signer" } ] }' ``` ```python Python Gen SDK theme={null} def create_sign_request(client: Client, template_id: str, signer_email: str): """Create sign request from template""" parent_folder = FolderMini( id=SIGN_DOCS_FOLDER, type=FolderBaseTypeField.FOLDER ) signer = SignRequestCreateSigner( email=signer_email, ) sign_request = client.sign_requests.create_sign_request( signers=[signer], parent_folder=parent_folder, template_id=template_id, ) return sign_request def main(): ... # Create sign request from template sign_request = create_sign_request(client, TEMPLATE_SIMPLE, SIGNER_A) check_sign_request(sign_request) ``` Resulting in (simplified): ```json theme={null} { "signers": [ { "email": "sender@example.com", "role": "final_copy_reader", }, { "email": "signer@example.com", "role": "signer", } ], "id": "71e86670-5850-44cc-8b4d-9f5eab6c04de", "parent_folder": { "id": "234102987614", "type": "folder", "name": "signed docs" }, "name": "Simple-DOC (1).pdf", "type": "sign-request", "status": "created", "sign_files": { "files": [ { "id": "1393030489686", "type": "file", "name": "Simple-DOC (1).pdf", } ], }, "template_id": "f2ec720d-47a6-4052-8210-9bfa8d6c349c" } ``` ```yaml theme={null} Simple sign request: b25674a2-540b-4201-ae18-a78f05ef1a9a Status: created Signers: 2 final_copy_reader: sender@example.com signer: signer@example.com Prepare url: None ``` The signer receives an email from Box.com with a link to the document, and can sign it. Since the template already had the signature requirements, document preparation was not needed. Notice the date was automatically populated with the current date. ## Pre-populate the signature attributes From a usability perspective, it is a good idea to pre-populate the inputs you require from your users. Some inputs may be intentionally left unpopulated. For example, when your legal department specifies that the “Yes, I agree” field must be explicitly set by the signer. Using the Box app sign template editor, you can assign an `external_id` to each of the inputs, and have the app populate them from any data source. Let’s implement this for the name. Go back to the template design and add an id to the name field: Assigning a tag id to a signature property input Save the template. Let’s create a new method to pre-populate the name: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_requests' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer E9..Q0' \ --data-raw '{ "template_id": "f2ec720d-47a6-4052-8210-9bfa8d6c349c", "parent_folder": { "id": "234102987614", "type": "folder" }, "signers": [ { "email": "signer@example.com", "role": "signer" } ], "prefill_tags": [ { "document_tag_id": "signer_full_name", "text_value": "Signer A" } ] }' ``` ```python Python Gen SDK theme={null} def create_sign_request_name_default( client: Client, template_id: str, signer_name, signer_email: str ): """Create sign request from template""" parent_folder = FolderMini( id=SIGN_DOCS_FOLDER, type=FolderBaseTypeField.FOLDER ) signer = SignRequestCreateSigner( email=signer_email, ) # tags tag_full_name = SignRequestPrefillTag( document_tag_id="signer_full_name", text_value=signer_name, ) sign_request = client.sign_requests.create_sign_request( signers=[signer], parent_folder=parent_folder, prefill_tags=[tag_full_name], template_id=template_id, ) return sign_request def main(): ... # Create sign request from template with name sign_request_name = create_sign_request_name_default( client, TEMPLATE_SIMPLE, "Signer A", SIGNER_A ) check_sign_request(sign_request_name) ``` Resulting in (simplified): ```json theme={null} { "signers": [ { "email": "sender@example.com", "role": "final_copy_reader", }, { "email": "signer@example.com", "role": "signer", "is_in_person": false, } ], "id": "6f42a041-7ed8-4e08-9958-78a97259f80d", "prefill_tags": [ { "document_tag_id": "signer_full_name", "text_value": "Signer A", } ], "parent_folder": { "id": "234102987614", "type": "folder", "name": "signed docs" }, "name": "Simple-DOC (2).pdf", "type": "sign-request", "status": "created", "sign_files": { "files": [ { "id": "1393047116817", "type": "file", "name": "Simple-DOC (2).pdf", } ], }, "template_id": "f2ec720d-47a6-4052-8210-9bfa8d6c349c" } ``` ```yaml theme={null} Simple sign request: adab1740-eeba-4392-a3f5-defddc79c946 Status: created Signers: 2 final_copy_reader: sender@example.com signer: signer@example.com Prepare url: None ``` Open the signer inbox and complete the sign request. Signing the document When the signer views the document, the `signer` can still change it. ## Get more information about a template You've seen that you can list the templates available to a user. But you can also get more information about a specific template. Let’s create a method that returns basic information of a template, but details all the signature requirements: ```sh cURL theme={null} curl --location 'https://api.box.com/2.0/sign_templates/ f2ec720d-47a6-4052-8210-9bfa8d6c349c' \ --header 'Authorization: Bearer OL..BQ' ``` ```python Python Gen SDK theme={null} def sign_template_print_info(client: Client, template_id: str): sign_template = client.sign_templates.get_sign_template_by_id(template_id) print(f"\nSign template: {sign_template.id} - {sign_template.name}") print(f" Signers: {len(sign_template.signers)}") for signer in sign_template.signers: print(f" {signer.role.value}") if len(signer.inputs) > 0: print(" Tag ID\t Type\t Required") for input in signer.inputs: print( f" {input.document_tag_id} {input.type.value} {input.is_required}" ) def main(): ... # Print sign template details sign_template_print_info(client, TEMPLATE_SIMPLE) ``` Resulting in (simplified): ```json theme={null} { "type": "sign-template", "id": "f2ec720d-47a6-4052-8210-9bfa8d6c349c", "name": "Simple-DOC.pdf", "parent_folder": { "id": "234102987614", "type": "folder", "name": "signed docs" }, "source_files": [ { "id": "1393013714313", "type": "file", } ], "signers": [ { "email": "", "label": "", "role": "final_copy_reader", }, { "email": "", "label": "Signer", "role": "signer", "inputs": [ { "document_tag_id": null, "id": "d02c8e16-5050-475e-b74b-9a952193e4f8", "type": "date", "is_required": true, "date_value": null, "content_type": "date", }, { "document_tag_id": "signer_full_name", "id": "bdcc966e-2ebf-4b3b-aaee-99d4e1161a9e", "type": "text", "text_value": null, "is_required": true, "content_type": "full_name", }, { "document_tag_id": null, "id": "1a8f4cb1-5c09-46bd-96f5-0ab449f19640", "type": "signature", "is_required": true, "content_type": "signature", } ] } ], } ``` ```yaml theme={null} Sign template: 94e3815b-f7f5-4c2c-8a26-e9ba5c486031 - Simple-PDF.pdf Signers: 2 final_copy_reader signer Tag ID Type Required None date True signer_full_name text True None signature True ``` Notice that the `signer_full_name` is the `tag_id` we used to pre-populate the name. ## Summary Templates are a form of signing structured documents where the signature requirements are already defined and placed on the document. This not only keeps your contract management team happy, but it also creates a process which is consistent and requires a low level of effort from your users. Finally if your document signature requirements have a lot of options, you can pre-populate these from another data source and save the user some time. Remember that the user who owns these properties can always change them. There is no API entry point to create a template, so you will have to create and manage them manually from the Box app, unless the document already includes signature tags that can be used by the Box Sign engine. Take a look at our [Structured Docs][structured-docs] section for more information. [template]: https://support.box.com/hc/en-us/sections/21356768117651-Templates [request-options]: /sign/request-options [structured-docs]: /sign/technical-use-cases/sign-structured-docs [additional-sec]: /sign/request-options/extra-security # Signing unstructured docs Source: https://developer.box.com/sign/technical-use-cases/sign-unstructured-docs Imagine a document management app, where users can upload a document and ask anyone to sign it. In this case your app will know what document to sign and who needs to sign, but it has no idea where to put the signature or its properties like name, date, initial, and so on. This contrasts with [using templates][sign-templates] or [structured documents][sign-structured-docs] where your app knows what they are, and where the signature properties go. In these cases, and because each document can have a different structure, it is a good idea to always set the `is_document_preparation_needed` flag set to `true`, so that the sender has a chance to select and place the signature properties in the document before the signer gets the request. There are three steps to this flow, creating the signature request, then preparing the document, and finally signing it. This is how the flow looks like: Sign unstructured docs flow Consider this 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 Gen SDK theme={null} def sign_doc_single( client: Client, document_id: str, destination_folder_id: str, signer_email: str, prep_needed: bool = False, ) -> SignRequest: # Sign request params source_file = FileBase(id=document_id, type=FileBaseTypeField.FILE) destination_folder = FolderMini( id=destination_folder_id, type=FolderBaseTypeField.FOLDER ) signer = SignRequestCreateSigner(signer_email) # sign document sign_request = client.sign_requests.create_sign_request( signers=[signer], parent_folder=destination_folder, source_files=[source_file], is_document_preparation_needed=prep_needed, ) return sign_request def main(): conf = ConfigOAuth() client = get_client_oauth(conf) # Simple sign a pdf request with preparation sign_pdf_prep = sign_doc_single( client, SIMPLE_PDF, SIGN_DOCS_FOLDER, SIGNER_A, True ) if sign_pdf_prep.prepare_url is not None: open_browser(sign_pdf_prep.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 Python Gen SDK 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/ ``` Notice in the above script that, if a prepare document URL was generated by the signature request, then the app opens a browser for it. The requester can then apply the different signature properties, for example: Preparing the document using drag and drop on the template editor Once the document is prepared, the requester can send the signature request to the signer. Back in the Box app you can see the status `In Progress`. Pending signature request The signer then receives an email from Box with a link to the signature request. Signing the document When the process is completed, both a signature log containing metadata and the signed document are stored in the destination folder. Log and signed document [sign-templates]: /sign/technical-use-cases/sign-template [sign-structured-docs]: /sign/technical-use-cases/sign-structured-docs # Sign webhooks Source: https://developer.box.com/sign/webhooks/index Sign webhooks allow you to receive notifications about events that happen with Box Sign signature requests. You can use them to trigger actions in your own application, or to notify your users about events that happen in Box Sign. This is particularly important since the signature requests are asynchronous, and the signers can interact with them at any time, possibly outside of your application. ## Sign-related events There are Box Sign-related events that can trigger the webhooks. Like most of Box events the listeners are set at the folder or document level. The most common use case is to listen to the events at the folder where the signature requests are created. This way you can listen to all the signature requests created in that folder. Some examples of events that can be listened to are: * `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 the signature request is signed by a particular signer. * `SIGN_REQUEST.SIGNATURE_REQUESTED`, when the signature is requested from the signer. * `SIGN_REQUEST.ERROR_FINALIZING`, when the signature request could not be processed. # 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/box-for-ai ``` To browse the full list of available skills, see the [Box skills registry](https://skills.sh/box/box-for-ai). 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 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 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. 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. 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. # 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. ## 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 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. ## 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#ty9l3). * 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 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 -L 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. | \`\`\`\` | | **`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 -L 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/ # 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 Scout 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/ # 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}`, and I have $8000 and want to spend a week in the Azores. What should I see?", }, "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 POST "https://api.box.com/2.0/ai/ask" \ -H "content-type: application/json" \ -H "authorization: " \ -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": "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 POST "https://api.box.com/2.0/ai/text_gen" \ -H "content-type: application/json" \ -H "authorization: Bearer TOKEN" \ -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 POST "https://api.box.com/2.0/ai/text_gen" \ -H "content-type: application/json" \ -H "authorization: Bearer TOKEN" \ -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_4o_2024_05_13" } } } ``` 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 'https://api.box.com/2.0/ai/extract' \ -H 'content-type: application/json' \ -H 'authorization: Bearer TOKEN' \ -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 'https://api.box.com/2.0/ai/extract' \ -H 'content-type: application/json' \ -H 'authorization: Bearer TOKEN' \ -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_4o_2024_05_13" } } }' ``` 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 '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}`, and I have $8000 and want to spend a week in the Azores. What should I see?", }, "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 --location '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 --location 'https://api.box.com/2.0/ai/extract' \ --header 'Content-Type: application/json' \ --header 'Authorization: ' \ --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 --location '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 Box AI API, you can extract metadata from the provided file and get the result in the form of key-value pairs. As input, you can either create a structure using the `fields` parameter, or use an already defined metadata template. To learn more about creating templates, see [Creating metadata templates in the Admin Console][templates-console] or use the metadata template API. You can also [autofill metadata in templates][autofill-metadata] using our Standard or 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 automatically applies optical character recognition (OCR) when processing image files (TIFF, PNG, JPEG) and scanned documents. This eliminates the need to convert images to PDF before extraction, saving time and simplifying your integration. ## 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) No additional configuration is required to use different languages or image formats. Box AI automatically detects the language and applies OCR when needed. ## 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_structured` endpoint. ```sh cURL theme={null} curl -i -L '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." } ], "metadata_template": { "template_key": "", "type": "metadata_template", "scope": "" }, "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 the your first name?', type: 'string', } satisfies AiExtractStructuredFieldsField, { key: 'lastName', displayName: 'Last name', description: 'Person last name', prompt: 'What is the 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 the your first name?", type="string", ), CreateAiExtractStructuredFields( key="lastName", display_name="Last name", description="Person last name", prompt="What is the 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 the your first name?", Type = "string" },new AiExtractStructuredFieldsField(key: "lastName") { DisplayName = "Last name", Description = "Person last name", Prompt = "What is the 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 the your first name?", type: "string"), AiExtractStructuredFieldsField(key: "lastName", displayName: "Last name", description: "Person last name", prompt: "What is the 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 the your first name?").type("string").build(), new AiExtractStructuredFieldsField.Builder("lastName").description("Person last name").displayName("Last name").prompt("What is the 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 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 | | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | | **`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 that can either be `global` or `enterprise`. Global templates are those available to any Box enterprise, whereas `enterprise` templates are bound to a specific enterprise. | `metadata_template` | | **`metadata_template.template_key`** | The name of your metadata template. | `invoice` | | **`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` | | `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. | | | `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 if using `taxonomy` type 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 field. | `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. It includes but is not limited to `string`, `float`, `date`, `enum`, `multiSelect`, `struct`, `table`. | `string` | | `fields.taxonomy_key` | The identifier for a taxonomy, which corresponds to the `key` of the taxonomy source. Required if using `taxonomy` type 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 existing scalar types (`string`, `float`, `date`, `enum`, `multiSelect`). The `struct` and `table` types allow 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. Example: 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[]` (e.g. `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 correctly extracts repeating data whether it appears as 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 The following types are supported within both struct and table fields. | 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. ## Use cases This example shows you how to extract metadata from a sample invoice in a structured way. Let's assume you want to extract the vendor name, invoice number, and a few more details. sample invoice ### Create the request To get the response from Box AI, call `POST /2.0/ai/extract_structured` endpoint with the following parameters: * `items.type` and `items.id` to specify the file to extract the data from. * `fields` to specify the data that you want to extract from the given file. * `metadata_template` to supply an already existing metadata template. You can use either `fields` or `metadata_template` to specify your structure, but not both. ### Use `fields` parameter The `fields` parameter allows you to specify the data you want to extract. Each `fields` object has a subset of parameters you can use to add more information about the searched data. For example, you can add the field type, description, or even a prompt with some additional context. ```bash theme={null} curl --location '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: ```bash 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 --location '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: ```bash 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: ```bash 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 sample code snippet using 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 use cases for the Box AI Extract API, including structured and freeform metadata extraction across industries. 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). * Run extractions without building your own orchestration, permission checks, or throttling logic around the extraction workflow. * 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 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. ## Try it yourself Follow a step-by-step walkthrough that builds a working accounts payable automation: watch a folder for new invoices, extract structured fields, and write metadata back to each file. ## Next steps Get started with the Box Extract API with practical example-led quick starts 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 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 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 an 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 Technical reference for Box AI request handling, input limits, model behavior, and the agent override system. This section contains developer guides for working with Box AI. For a summary of API capabilities, quick starts, and reference links, see the [Box AI API](/ai/box-ai-api) page. The below guides cover what you need to know before you start writing code: how Box AI processes requests, what limits apply to different endpoints, how to control model behavior through agent overrides, and where to find the specific tutorial for your use case. 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 | ### 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. 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 Model Context Protocol ([MCP](https://modelcontextprotocol.io/introduction)) is an open protocol that standardizes how applications provide context to LLMs. MCP servers make building advanced integrations simpler and less time consuming. The **Box MCP server** lets AI agents and apps use Box content through a single hosted endpoint. Your users authorize access with OAuth; agents call tools (search, Box AI, folders, and more) without you handling raw file payloads in the client. 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 | | ------------------------------------------------------------------------- | ---------------------------------------------- | | 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 API using the `PUT /files/:file_id` or `PUT /files/:file_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. | | `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 limited content preview (Preview Light). Works only for shared 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 `