> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Box

> Client-mode REST API connectors for Box: upload shift reports, batch records and setpoint tables from MQTT as files or new versions, download recipes and configs, list, copy and move folders and files to archive, tag files with metadata and query by it, search, share with shared links, read collaborations, comment, open review tasks, register V2 webhooks and poll the event stream

Technical Preview

## Overview

Client-mode connectors for the [Box Platform API](https://developer.box.com/reference) that move plant files between the broker and Box. A typical flow writes a shift report, batch record or alarm snapshot as a JSON or CSV file into a plant folder, uploads today's setpoint table as a new version of the same file, reads a recipe back when a line changes over, tags a batch record with metadata (line, batch, product, quality state) and releases it with a metadata update, finds files by metadata or by search, moves processed files to an archive folder, and shares a weekly report with a shared link. New files in an inbox folder can be picked up with a V2 webhook (needs a public HTTPS receiver) or by polling the user event stream with a stream position.

Box serves uploads from a separate host, so the template file `BoxRestApi.lot` holds two connectors:

| Connector | Base address | Format | Events |
| - | - | - | - |
| `BoxRestApi` | `https://api.box.com` | JSON API under `/2.0/...`, and `/oauth2/token` for access tokens | 42 |
| `BoxUploadRestApi` | `https://upload.box.com` | `multipart/form-data` uploads under `/api/2.0/files/content` (new file) and `/api/2.0/files/{file_id}/content` (new version) | 2 |

The connectors carry 44 events (24 GET, 14 POST, 6 PUT; 18 writes, no delete). Topics use `storage/box/<object>/<verb>`; each response is published on `<source topic>/response`.

> [!NOTE]
>
> The template was checked against the [Box OpenAPI spec](https://github.com/box/box-openapi) (version 2024.0: host, method, path, path and required query parameters, required headers, request content type, and every example body, query and upload attribute against the request schema) and against a local mock of the API, upload and download hosts. Box checks the token before it connectors a request on `api.box.com`, so without a token every event answers `400 Malformed auth header` (empty token) or `401 invalid_token` (dummy token). On `upload.box.com` the two upload paths answered `401` while the same paths with a suffix answered `400 Invalid API request path`. `/oauth2/token` was called live and rejected dummy client credentials and an unknown `box_subject_type`. Nothing has run with a real token yet, so the status stays blocked until an authenticated read and an upload pass.

## Setup

1. In the [Box Developer Console](https://app.box.com/developers/console), create a **Platform App** (Custom App) with **Server Authentication (Client Credentials Grant)**. JWT server authentication works the same way, but the broker then needs a signed JWT assertion for each token request.
2. Under **App Access Level**, keep **App Access Only** (the app works as its own service account) or pick **App + Enterprise Access** if it must act as a managed user. Under **Application Scopes**, enable **Read all files and folders stored in Box** and **Write all files and folders stored in Box** (`root_readwrite`), **Manage webhooks** (`manage_webhook`) for the webhook events, and **Manage enterprise properties** (`manage_enterprise_properties`) only if the broker reads enterprise metadata templates.
3. Authorize the app in the Box Admin Console (free developer accounts authorize it automatically) and reauthorize it after every scope change.
4. Note the **Client ID** and **Client Secret** (Configuration tab) and the **Enterprise ID** (General Settings). Keep them in broker secrets, for example `BOX_CLIENT_ID`, `BOX_CLIENT_SECRET` and `BOX_ENTERPRISE_ID`, and the current token in `BOX_ACCESS_TOKEN`. Inject them in a LoT action with `GET SECRET`.
5. The service account has its own, empty root folder. Invite it (its login looks like `AutomationUser_…@boxdevedition.com`, returned by `UserMe`) as an **Editor** collaborator on the plant folders the broker should use, and use those folder ids in payloads.

## Authentication

Every event except `OAuthToken` sends `Authorization: Bearer <token>` built from the `access_token` field of the payload:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "YOUR_BOX_ACCESS_TOKEN"}
```

Get the token with `OAuthToken` (`storage/box/oauth/token/create`). It sends no `Authorization` header; the client credentials travel in the form. Use `box_subject_type` `enterprise` with the enterprise id for the service account, or `user` with a user id for a managed user (needs App + Enterprise Access and **Generate user access tokens**):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"form": "grant_type=client_credentials&client_id=<BOX_CLIENT_ID>&client_secret=<BOX_CLIENT_SECRET>&box_subject_type=enterprise&box_subject_id=<BOX_ENTERPRISE_ID>"}
```

Box answers with `access_token` and `expires_in` (about 3600 seconds). The Client Credentials Grant returns no refresh token, so request a new token before the old one expires. JWT apps send `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<signed JWT>&client_id=...&client_secret=...` instead.

> [!WARNING]
>
> The `OAuthToken` request carries the client secret and the response carries the access token. Restrict `storage/box/oauth/#` with ACLs, never retain those topics, and store the new token in the secret instead of passing it around on open topics.

## Payloads

| Field | Used by | Content |
| - | - | - |
| `access_token` | every event except `OAuthToken` | OAuth 2.0 access token |
| `folder_id`, `file_id`, `webhook_id`, `collaboration_id` | events with that id in the path | Box ids as strings. Folder `"0"` is the root (All Files). |
| `scope`, `template_key` | metadata events | `enterprise` (or `global` for `properties`) and the template key, for example `batchRecord` |
| `query` | GET events with `?{value.json.query}` | URL query string without `?`, for example `fields=id,name,size&limit=100`, or `""` |
| `body` | POST and PUT events | JSON request body; a JSON array of JSON Patch operations for `FileMetadataUpdate` |
| `form` | `OAuthToken` | URL-encoded token request |
| `shared_link` | `SharedItemGet` | Shared link URL, sent as the `BoxApi: shared_link=<url>` header |
| `multipart` | `FileUpload`, `FileVersionUpload` | Complete `multipart/form-data` body (see below) |

Send `query` as an empty string when you need no parameters; the connector then calls, for example, `/2.0/users/me?`. Use `fields` to keep responses small enough for MQTT.

## Uploading files

Box takes uploads as `multipart/form-data` with two parts: `attributes` (JSON with `name` and, for a new file, `parent.id`) and then `file`. The `attributes` part must come first; otherwise Box answers `400 metadata_after_file_contents`. The upload connector sets `Content-Type: multipart/form-data; boundary=CorefluxBoxBoundary`, so the payload field `multipart` must hold the whole body with that boundary and CRLF line ends:

```text theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
--CorefluxBoxBoundary
Content-Disposition: form-data; name="attributes"

{"name":"2026-10-05-shift-A.json","parent":{"id":"1001"}}
--CorefluxBoxBoundary
Content-Disposition: form-data; name="file"; filename="2026-10-05-shift-A.json"
Content-Type: application/json

{"line":"2","shift":"A","oee":0.87,"scrap":14}
--CorefluxBoxBoundary--
```

As a JSON payload on `storage/box/files/upload` (build the string in a LoT action or on the publishing client; `\r\n` are the CRLF line ends):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "<token>", "multipart": "--CorefluxBoxBoundary\r\nContent-Disposition: form-data; name=\"attributes\"\r\n\r\n{\"name\":\"2026-10-05-shift-A.json\",\"parent\":{\"id\":\"1001\"}}\r\n--CorefluxBoxBoundary\r\nContent-Disposition: form-data; name=\"file\"; filename=\"2026-10-05-shift-A.json\"\r\nContent-Type: application/json\r\n\r\n{\"line\":\"2\",\"shift\":\"A\",\"oee\":0.87,\"scrap\":14,\"operator\":\"Jo\\u00e3o\"}\r\n--CorefluxBoxBoundary--\r\n"}
```

Box answers `201` with a collection holding the new file (`id`, `sha1`, `file_version`). If a file with that name already exists in the folder, Box answers `409 item_name_in_use`; upload a new version instead with `FileVersionUpload` (`storage/box/files/versions/upload`, same body, `file_id` in the payload, `attributes` with only `name`). Direct uploads accept up to 50 MB, but keep MQTT payloads small.

## Examples

List a folder (`storage/box/folders/items/list`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "<token>", "folder_id": "1001", "query": "fields=id,type,name,size,modified_at&limit=100"}
```

Read a recipe back on changeover (`storage/box/files/download`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "<token>", "file_id": "2001", "query": ""}
```

Box answers `302` with a short-lived `dl.boxcloud.com` link, which needs no token. The broker's HTTP client must follow it to publish the file content. Keep downloads to text and JSON files that fit an MQTT message.

Tag a batch record and release it later (`storage/box/files/metadata/create`, then `storage/box/files/metadata/update` with JSON Patch):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "<token>", "file_id": "2001", "scope": "enterprise", "template_key": "batchRecord", "body": {"line": "2", "batch": "1005-A", "product": "PX-200", "qualityState": "pending"}}
```

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "<token>", "file_id": "2001", "scope": "enterprise", "template_key": "batchRecord", "body": [{"op": "test", "path": "/qualityState", "value": "pending"}, {"op": "replace", "path": "/qualityState", "value": "released"}]}
```

The `test` operation makes the update fail with `409` if somebody changed the value in the meantime.

Find released batch records of line 2 (`storage/box/metadata/query`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "<token>", "body": {"from": "enterprise_12345.batchRecord", "ancestor_folder_id": "1001", "query": "line = :line AND qualityState = :state", "query_params": {"line": "2", "state": "released"}, "fields": ["name", "metadata.enterprise_12345.batchRecord.batch"], "limit": 100}}
```

Archive a processed file instead of deleting it (`storage/box/files/update`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "<token>", "file_id": "2001", "body": {"parent": {"id": "1009"}, "name": "2026-10-05-shift-A.json", "tags": ["processed"]}}
```

Share a weekly report inside the company (`storage/box/files/shared-link/add`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "<token>", "file_id": "2001", "body": {"shared_link": {"access": "company", "permissions": {"can_download": true}}}}
```

Watch an inbox folder for new files with a V2 webhook (`storage/box/webhooks/create`). The `address` must be HTTPS on port 443:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "<token>", "body": {"target": {"id": "1006", "type": "folder"}, "address": "https://broker.example.com/box/webhooks", "triggers": ["FILE.UPLOADED", "FILE.MOVED"]}}
```

Or poll the event stream without a public endpoint (`storage/box/events/list`):

1. Get the current position: `{"access_token": "<token>", "query": "stream_position=now"}` returns `next_stream_position` and no events.
2. Ask for changes since then: `{"access_token": "<token>", "query": "stream_type=changes&stream_position=<next_stream_position>&limit=100"}`. Store the new `next_stream_position` after every call. Box may return an event more than once or out of order, so de-duplicate by `event_id`.

## Events

| Event | Connector | Method | Endpoint | Source topic | Write | Notes |
| - | - | - | - | - | - | - |
| `UserMe` | API | `GET` | `/2.0/users/me?{value.json.query}` | `storage/box/users/me/get` | | Smoke: the user behind the token (for client credentials: the app's service account, or the managed user from box\_subject\_type=user). query e.g. `fields=id,name,login,enterprise` (or empty). |
| `OAuthToken` | API | `POST` | `/oauth2/token` | `storage/box/oauth/token/create` | | Get an access token (about 60 minutes; Client Credentials Grant has no refresh token, so ask again before expiry). No Authorization header; credentials go in the form. form: `grant_type=client_credentials&client_id=<BOX_CLIENT_ID>&client_secret=<BOX_CLIENT_SECRET>&box_subject_type=enterprise&box_subject_id=<BOX_ENTERPRISE_ID>` (or box\_subject\_type=user\&box\_subject\_id=`<user id>`; JWT apps send grant\_type=urn:ietf:params:oauth:grant-type:jwt-bearer\&assertion=`<signed JWT>`\&client\_id=...\&client\_secret=...). The response carries access\_token: never retain it, restrict storage/box/oauth/# with ACLs. |
| `FolderGet` | API | `GET` | `/2.0/folders/{value.json.folder_id}?{value.json.query}` | `storage/box/folders/get` | | Folder info (name, path\_collection, item\_collection, size, modified\_at, shared\_link). folder\_id "0" is the root (All Files). query e.g. `fields=id,name,path_collection,item_status` (or empty). |
| `FolderItemsList` | API | `GET` | `/2.0/folders/{value.json.folder_id}/items?{value.json.query}` | `storage/box/folders/items/list` | | Catalog 'GET folders/`{id}`/items'. Files, folders and web links in a folder. query e.g. `fields=id,type,name,size,modified_at,sha1&limit=1000&usemarker=true` (then marker=`<next_marker>`), or sort=date\&direction=DESC\&offset=0. |
| `FolderCreate` | API | `POST` | `/2.0/folders` | `storage/box/folders/create` | Yes | Create a folder, e.g. one per line, batch or day. body: `{"name":"2026-10-05","parent":{"id":"<line-2 folder id>"}}`. 409 item\_name\_in\_use if it exists (the error context\_info holds the existing folder). |
| `FolderUpdate` | API | `PUT` | `/2.0/folders/{value.json.folder_id}` | `storage/box/folders/update` | Yes | Rename or move a folder, or set its description and tags (archive a finished batch folder instead of deleting it). body: `{"name":"batch-1005-A (closed)","parent":{"id":"<archive folder id>"},"description":"Closed 2026-10-05 by line 2","tags":["closed","line-2"]}`. |
| `FolderCopy` | API | `POST` | `/2.0/folders/{value.json.folder_id}/copy` | `storage/box/folders/copy` | Yes | Copy a folder tree, e.g. a template folder for a new work order. body: `{"parent":{"id":"<maintenance folder id>"},"name":"WO-4711"}`. |
| `FolderSharedLinkAdd` | API | `PUT` | `/2.0/folders/{value.json.folder_id}?fields=shared_link` | `storage/box/folders/shared-link/add` | Yes | Create or change the shared link of a folder (e.g. a report folder for a customer). body: `{"shared_link":{"access":"company","permissions":{"can_download":true},"unshared_at":"2026-12-31T23:59:59Z"}}` (access open / company / collaborators; unshared\_at needs a paid account). Returns shared\_link.url. |
| `FolderCollaborationsList` | API | `GET` | `/2.0/folders/{value.json.folder_id}/collaborations?{value.json.query}` | `storage/box/folders/collaborations/list` | | Who has access to a folder and with which role (editor, viewer, uploader, ...). query e.g. `fields=id,role,accessible_by,status&limit=100` (or empty). |
| `FolderMetadataList` | API | `GET` | `/2.0/folders/{value.json.folder_id}/metadata` | `storage/box/folders/metadata/list` | | All metadata instances on a folder (every template applied to it). folder\_id "0" is not allowed for metadata. |
| `FolderMetadataGet` | API | `GET` | `/2.0/folders/{value.json.folder_id}/metadata/{value.json.scope}/{value.json.template_key}` | `storage/box/folders/metadata/get` | | One metadata instance on a folder. scope enterprise (or global for properties); template\_key e.g. plantFolder. 404 instance\_not\_found if it is not applied. |
| `FolderMetadataCreate` | API | `POST` | `/2.0/folders/{value.json.folder_id}/metadata/{value.json.scope}/{value.json.template_key}` | `storage/box/folders/metadata/create` | Yes | Apply a metadata template to a folder. body: the field values, e.g. `{"plant":"Porto","line":"2","costCenter":"PT-OPS-02"}` (keys = template field keys). 409 tuple\_already\_exists if applied; use the file-style JSON Patch update then. |
| `FileGet` | API | `GET` | `/2.0/files/{value.json.file_id}?{value.json.query}` | `storage/box/files/get` | | File info (name, size, sha1, version, parent, modified\_at, lock, shared\_link). query e.g. `fields=id,name,size,sha1,file_version,modified_at,parent,lock` (or empty). |
| `FileUpdate` | API | `PUT` | `/2.0/files/{value.json.file_id}` | `storage/box/files/update` | Yes | Rename or move a file (archive a processed report instead of deleting it), set description or tags, or lock it while a line uses it. body: `{"parent":{"id":"<archive folder id>"},"name":"2026-10-05-shift-A.json","tags":["processed"]}` or `{"lock":{"access":"lock","is_download_prevented":false}}`. |
| `FileCopy` | API | `POST` | `/2.0/files/{value.json.file_id}/copy` | `storage/box/files/copy` | Yes | Copy a file, e.g. a recipe template into a batch folder. body: `{"parent":{"id":"<batch folder id>"},"name":"batch-1005-A-recipe.json"}` (optional version for an older file version). |
| `FileDownload` | API | `GET` | `/2.0/files/{value.json.file_id}/content?{value.json.query}` | `storage/box/files/download` | | Read a file back (recipe, config, setpoint table). Box answers 302 with a short-lived dl.boxcloud.com location; the connector must follow it to return the bytes (202 with Retry-After if the file is not ready). query e.g. `version=<file version id>` (or empty). Text and JSON files only through MQTT. |
| `FileVersionsList` | API | `GET` | `/2.0/files/{value.json.file_id}/versions?{value.json.query}` | `storage/box/files/versions/list` | | Earlier versions of a file (who changed a recipe or config and when; the current version is not in the list). query e.g. `fields=id,sha1,size,modified_at,modified_by&limit=100` (or empty). Needs a paid account. |
| `FileVersionPromote` | API | `POST` | `/2.0/files/{value.json.file_id}/versions/current` | `storage/box/files/versions/promote` | Yes | Roll back a file to an earlier version (a copy of it becomes the new current version). body: `{"id":"<file version id from FileVersionsList>","type":"file_version"}`. |
| `FileSharedLinkAdd` | API | `PUT` | `/2.0/files/{value.json.file_id}?fields=shared_link` | `storage/box/files/shared-link/add` | Yes | Create or change the shared link of a file (e.g. a weekly OEE report). body: `{"shared_link":{"access":"company","permissions":{"can_download":true}}}` (access open / company / collaborators; optional password, vanity\_name, unshared\_at). Returns shared\_link.url and download\_url. |
| `FileSharedLinkGet` | API | `GET` | `/2.0/files/{value.json.file_id}?fields=shared_link` | `storage/box/files/shared-link/get` | | The shared link of a file (url, download\_url, access, effective\_permission, unshared\_at), or null if none. |
| `SharedItemGet` | API | `GET` | `/2.0/shared_items` | `storage/box/shared-items/get` | | Resolve a shared link to its file or folder (e.g. a supplier's spec sheet shared with the plant), then use FileGet / FileDownload with the returned id. shared\_link: the link URL, e.g. [https://app.box.com/s/abc123](https://app.box.com/s/abc123) (append \&shared\_link\_password=`<password>` if it has one); sent as header BoxApi: shared\_link=`<url>`. |
| `FileCollaborationsList` | API | `GET` | `/2.0/files/{value.json.file_id}/collaborations?{value.json.query}` | `storage/box/files/collaborations/list` | | Who has access to a file directly (not inherited from folders). query e.g. `fields=id,role,accessible_by,status&limit=100` (or empty). |
| `CollaborationGet` | API | `GET` | `/2.0/collaborations/{value.json.collaboration_id}` | `storage/box/collaborations/get` | | One collaboration (item, accessible\_by user or group, role, status, expires\_at). |
| `FileMetadataList` | API | `GET` | `/2.0/files/{value.json.file_id}/metadata` | `storage/box/files/metadata/list` | | All metadata instances on a file (batch, asset, quality state, ...). |
| `FileMetadataGet` | API | `GET` | `/2.0/files/{value.json.file_id}/metadata/{value.json.scope}/{value.json.template_key}` | `storage/box/files/metadata/get` | | One metadata instance on a file. scope enterprise (or global for properties); template\_key e.g. batchRecord. 404 instance\_not\_found if it is not applied. |
| `FileMetadataCreate` | API | `POST` | `/2.0/files/{value.json.file_id}/metadata/{value.json.scope}/{value.json.template_key}` | `storage/box/files/metadata/create` | Yes | Tag a file with structured metadata, e.g. a batch record with line, batch, product and quality state. body: `{"line":"2","batch":"1005-A","product":"PX-200","qualityState":"pending"}` (keys = template field keys; global/properties takes any string keys). 409 tuple\_already\_exists if applied. |
| `FileMetadataUpdate` | API | `PUT` | `/2.0/files/{value.json.file_id}/metadata/{value.json.scope}/{value.json.template_key}` | `storage/box/files/metadata/update` | Yes | Change metadata values with JSON Patch (Content-Type application/json-patch+json), e.g. release a batch. body: `[{"op":"test","path":"/qualityState","value":"pending"},{"op":"replace","path":"/qualityState","value":"released"}]` (op add / replace / test / remove / move / copy; remove only clears one field). |
| `MetadataQuery` | API | `POST` | `/2.0/metadata_queries/execute_read` | `storage/box/metadata/query` | | Find files and folders by metadata values (read-only although it is a POST), e.g. all released batch records of line 2. body: `{"from":"enterprise_<BOX_ENTERPRISE_ID>.batchRecord","ancestor_folder_id":"<plant folder id>","query":"line = :line AND qualityState = :state","query_params":{"line":"2","state":"released"},"fields":["name","metadata.enterprise_<BOX_ENTERPRISE_ID>.batchRecord.batch"],"limit":100}`. Page with marker. |
| `MetadataTemplatesList` | API | `GET` | `/2.0/metadata_templates/enterprise?{value.json.query}` | `storage/box/metadata-templates/list` | | Metadata templates of the enterprise (templateKey, displayName, fields). query e.g. `limit=100` (or empty). |
| `MetadataTemplateSchemaGet` | API | `GET` | `/2.0/metadata_templates/{value.json.scope}/{value.json.template_key}/schema` | `storage/box/metadata-templates/schema/get` | | Fields of one template (keys, types, enum options) before writing metadata. scope enterprise; template\_key e.g. batchRecord. |
| `Search` | API | `GET` | `/2.0/search?{value.json.query}` | `storage/box/items/search` | | Search names, descriptions, tags and file content under folders. query (URL-encoded) e.g. query=press-7\&type=file\&file\_extensions=pdf,csv\&ancestor\_folder\_ids=`<plant folder id>`\&fields=id,name,parent,modified\_at\&limit=50 (sort=modified\_at\&direction=DESC). Box indexes new and changed files with a delay, so a file just uploaded may not be found yet. |
| `FileCommentsList` | API | `GET` | `/2.0/files/{value.json.file_id}/comments?{value.json.query}` | `storage/box/files/comments/list` | | Comments on a file (e.g. a technician's notes on an inspection report). query e.g. `fields=id,message,created_by,created_at&limit=100` (or empty). |
| `CommentCreate` | API | `POST` | `/2.0/comments` | `storage/box/comments/create` | Yes | Comment on a file, e.g. attach an alarm summary to the shift report. body: `{"message":"Press 7 stopped 14 min (bearing temperature 92 C)","item":{"type":"file","id":"<file id>"}}` (item type comment replies to a comment). |
| `TaskCreate` | API | `POST` | `/2.0/tasks` | `storage/box/tasks/create` | Yes | Ask for a review or completion of a file, e.g. QA sign-off of a batch record. body: `{"item":{"type":"file","id":"<file id>"},"action":"review","message":"Release batch 1005-A","due_at":"2026-10-06T12:00:00Z","completion_rule":"any_assignee"}` (assign people afterwards in Box or with task assignments, not packaged). |
| `WebhookCreate` | API | `POST` | `/2.0/webhooks` | `storage/box/webhooks/create` | Yes | Catalog 'POST webhooks'. Get a notification (V2 webhook) when files arrive in a folder. body: `{"target":{"id":"<inbox folder id>","type":"folder"},"address":"https://broker.example.com/box/webhooks","triggers":["FILE.UPLOADED","FILE.MOVED"]}`. address must be HTTPS on port 443; one webhook per target and app. Verify box-signature-primary / secondary with the app's signature keys on the receiver. |
| `WebhooksList` | API | `GET` | `/2.0/webhooks?{value.json.query}` | `storage/box/webhooks/list` | | Webhooks of this app for the user behind the token. query e.g. `limit=100` (then marker=`<next_marker>`), or empty. |
| `WebhookGet` | API | `GET` | `/2.0/webhooks/{value.json.webhook_id}` | `storage/box/webhooks/get` | | One webhook (target, address, triggers, created\_by). |
| `WebhookUpdate` | API | `PUT` | `/2.0/webhooks/{value.json.webhook_id}` | `storage/box/webhooks/update` | Yes | Change a webhook's address, target or triggers (e.g. move the receiver to a new broker). body: `{"address":"https://broker2.example.com/box/webhooks","triggers":["FILE.UPLOADED","FILE.MOVED","FILE.COPIED"]}`. |
| `EventsList` | API | `GET` | `/2.0/events?{value.json.query}` | `storage/box/events/list` | | User event stream without a public endpoint (polling alternative to webhooks). query: stream\_position=now first (returns next\_stream\_position only), then stream\_type=changes\&stream\_position=`<next_stream_position>`\&limit=100; store next\_stream\_position after every call. Events may repeat or arrive out of order: de-duplicate by event\_id. Enterprise admin\_logs needs an admin token. |
| `RecentItemsList` | API | `GET` | `/2.0/recent_items?{value.json.query}` | `storage/box/recent-items/list` | | Items the user accessed recently (last 90 days, at most 1000). query e.g. `fields=id,name,modified_at&limit=100` (or empty). |
| `TrashItemsList` | API | `GET` | `/2.0/folders/trash/items?{value.json.query}` | `storage/box/trash/items/list` | | Files and folders in the trash (e.g. find a recipe that someone deleted by mistake). query e.g. `fields=id,type,name,trashed_at,parent&limit=100` (or empty). |
| `FileTrashRestore` | API | `POST` | `/2.0/files/{value.json.file_id}` | `storage/box/files/trash/restore` | Yes | Restore a file from the trash (the inverse of a delete). body: `{}` to restore it in place, or `{"name":"recipe.json","parent":{"id":"<folder id>"}}` if the old folder is gone or the name is taken. |
| `FileUpload` | Upload | `POST` | `/api/2.0/files/content` | `storage/box/files/upload` | Yes | Catalog 'POST files/content'. Upload a new file (JSON record, CSV, text) from an MQTT payload; Box allows up to 50 MB in one request, keep MQTT payloads small. multipart: the whole multipart/form-data body with boundary CorefluxBoxBoundary: an attributes part `{"name":"2026-10-05-shift-A.json","parent":{"id":"<line-2 folder id>"}}` first, then a file part with the content (see the docs). 201 returns a collection with the new file (id, sha1, file\_version); 409 item\_name\_in\_use if the name exists (use FileVersionUpload). |
| `FileVersionUpload` | Upload | `POST` | `/api/2.0/files/{value.json.file_id}/content` | `storage/box/files/versions/upload` | Yes | Upload a new version of an existing file (e.g. today's setpoint table over yesterday's; older versions stay in FileVersionsList). multipart: same body as FileUpload; the attributes part carries the file name, e.g. `{"name":"setpoints.csv"}` (the spec marks name required; a different name renames the file). 201 returns the file with its new file\_version. |

## Limits

* Box allows about 1000 API requests per minute and 240 upload requests per minute per user, and 6 searches per second (60 per minute) per user. Above the limit it answers `429` with a `retry-after` header. Wait that long before you retry.
* Direct uploads accept files up to 50 MB. Larger files need chunked upload sessions, which are not packaged.
* `FolderItemsList` returns at most 1000 entries per page (use `usemarker=true` and `marker`), `Search` at most 200, `EventsList` at most 500.
* One webhook per item and app, at most 1000 webhooks per app and user, HTTPS on port 443 only.
* File versions are tracked on paid accounts only. Recent items cover the last 90 days or 1000 items.
* Box indexes new and changed files with a delay, so `Search` may not find a file that was just uploaded. `MetadataQuery` and `FolderItemsList` see it right away.

## Not packaged

* Delete events on purpose: files, folders, trash (permanent delete), file versions, metadata instances, shared link removal, collaborations, comments, tasks and webhooks. Archive files with `FileUpdate` or `FolderUpdate` (move) instead.
* Chunked upload sessions for files over 50 MB, the upload preflight check (`OPTIONS /2.0/files/content`), thumbnails, zip downloads and the long-poll real-time server (`OPTIONS /2.0/events`).
* The `As-User` header (act on behalf of another user from an admin token); request a user token with `box_subject_type=user` instead. The `If-Match` / `If-None-Match` headers and the `content-md5` upload header.
* Collaboration create and update (granting access), users and groups administration, legal holds, retention, Shield, Sign, Box AI, Hubs, Doc Gen and enterprise events (`admin_logs`).
* The webhook receiver. A receiver would be a separate `ENABLE_SERVER` REST\_API connector on a public HTTPS address that checks `BOX-SIGNATURE-PRIMARY` / `BOX-SIGNATURE-SECONDARY` (HMAC-SHA256 of the body plus `BOX-DELIVERY-TIMESTAMP` with the app's signature keys) and rejects old timestamps. `EventsList` covers the same need without a public endpoint.

## Suggested branches

`feat/rest-api-route-box` for the broker template and `feat/docs-rest-api-box` for this page.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.