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

# Box

> Set up Box OAuth2 credentials and obtain a refresh token (Node and browser).

## Overview

The Box resource uses OAuth2 against the [Box v2 API](https://developer.box.com/reference/) to:

* **`/folders/{id}/items`**, list folder items (root id is `0`)
* **`/files/{id}/content`**, download file bytes
* **`/files/{id}`** / **`/folders/{id}`**, fetch entry metadata
* **`/search`**, search files by name/content
* **`/oauth2/token`**, refresh access tokens

Box is similar to Dropbox in shape but uses **numeric folder/file IDs** rather than paths.
Mirage caches the path → ID mapping in the index store, so once you `ls /box/` the IDs are
remembered and subsequent reads don't pay the lookup cost.

Three credential modes are supported:

* **Developer token**, fastest first-run path. Click one button in the Box app console, paste
  the 32-char access token, and you're done. Lives 60 minutes; you regenerate by hand. No
  client\_id / refresh\_token / OAuth flow needed. Recommended for `mirage` exploration.
* **Code flow with client secret**, for Node/server long-running use. Needs `client_id`,
  `client_secret`, `refresh_token`.
* **PKCE flow**, for browsers. Needs only `client_id` and `refresh_token` (no secret in the bundle).

<Note>
  **Box rotates the refresh token on every refresh.** The token you copy out of the initial code
  exchange is only valid until the next refresh, after that, only the new rotated token works.
  The `BoxTokenManager` keeps the latest token in memory; pass `onRefreshTokenRotated` if you
  want to persist it across restarts (the browser example does this with `localStorage`).
</Note>

## Quick Start: Developer Token (60 minutes, no OAuth)

If you just want to try the resource against your own Box account, the developer token is the
fastest path, no redirect URI, no client secret, no curl exchange:

1. Go to [https://app.box.com/developers/console](https://app.box.com/developers/console) -> **Create New App** -> **Custom App** ->
   **User Authentication (OAuth 2.0)** -> name it "Mirage" -> **Create App**
2. On the **Configuration** tab, scroll to the **Developer Token** section near the bottom.
3. Click **Generate Developer Token**. Copy the 32-char token.
4. Paste into your `.env.development`:
   ```bash theme={null}
   BOX_DEVELOPER_TOKEN=...
   ```
5. Run any of the examples in [`examples/typescript/box/`](https://github.com/strukto-ai/mirage/tree/main/examples/typescript/box).

The token expires in **60 minutes** and there is no programmatic way to renew it; you regenerate
by hand. For long-running processes, use the OAuth flow below instead.

When `BOX_DEVELOPER_TOKEN` is set, the `BoxResource` skips the OAuth refresh path entirely and
calls Box directly with the token in the `Authorization: Bearer` header.

## Setup (long-running OAuth flow)

### 1. Create a Box App

1. Go to [https://app.box.com/developers/console](https://app.box.com/developers/console)
2. Click **Create New App**
3. Choose **Custom App**
4. **Authentication Method**: **User Authentication (OAuth 2.0)**
5. Name it (e.g. "Mirage") -> **Create App**

### 2. Configure the App

Open the app's **Configuration** tab:

* **OAuth 2.0 Redirect URIs**: add every destination you'll redirect back to. Box accepts
  multiple values, register them all up front so you don't bounce between dev and prod:
  * For the CLI flow below: `http://localhost:1`
  * For the browser PKCE example: `http://localhost:5173/box_pkce.html`
  * For production: e.g. `https://yourapp.com/box/callback`
* **Allowed Origins** (only needed if you'll call from a browser; older Box docs call this
  "CORS Domains"):
  * `http://localhost:5173`
  * Any production origins (e.g. `https://yourapp.com`)
* **Application Scopes**:
  * **Read all files and folders stored in Box** (required)
  * **Write all files and folders stored in Box** (only if you'll add write commands later)
* Click **Save Changes**

### 3. Submit for Authorization (enterprise apps only)

If your Box account is part of an enterprise, the **Authorization** tab requires an admin to
approve the app before it can issue tokens. Personal/free Box accounts skip this step.

### 4. Copy the Client Credentials

Still on **Configuration**, copy:

* **Client ID** -> `BOX_CLIENT_ID`
* **Client Secret** -> `BOX_CLIENT_SECRET` (skip if you only need PKCE)

### 5. Get the Refresh Token (CLI flow with client secret)

**A) Open this URL in a browser** (replace `YOUR_CLIENT_ID`):

```
https://account.box.com/api/oauth2/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:1
```

**B) Authorize:** sign in -> click **Grant access**.

**C) Copy the code:** browser redirects to `http://localhost:1?code=...` (page won't load,
expected). Copy the `code` from the URL bar.

**D) Exchange the code:**

```bash theme={null}
curl https://api.box.com/oauth2/token \
  -d "code=THE_CODE" \
  -d "grant_type=authorization_code" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "redirect_uri=http://localhost:1"
```

Response shape:

```json theme={null}
{
  "access_token": "T...",
  "expires_in": 3884,
  "refresh_token": "R...",
  "restricted_to": [],
  "token_type": "bearer"
}
```

Save the `refresh_token`.

### 6. Set Environment Variables

```bash theme={null}
# .env.development
BOX_CLIENT_ID=...
BOX_CLIENT_SECRET=...
BOX_REFRESH_TOKEN=...
```

## Alternative: PKCE Flow (browser, no client secret)

1. Add `http://localhost:5173/box_pkce.html` to **OAuth 2.0 Redirect URIs**
2. Add `http://localhost:5173` to **Allowed Origins**
3. Set only `BOX_CLIENT_ID` in `.env.development`
4. From `examples/typescript/browser/`, run `pnpm dev`
5. Open `http://localhost:5173/box_pkce.html` and click **Connect Box**

The example persists the rotated refresh token to `localStorage` via the
`onRefreshTokenRotated` callback so subsequent page loads work without re-auth.

## Token Lifetime

| Token         | Lifetime                                                 |
| ------------- | -------------------------------------------------------- |
| Access token  | \~1 hour (`expires_in: ~3600`)                           |
| Refresh token | 60 days from issue, OR 60 days from last use (whichever) |

A refresh token gets revoked when:

* 60 days pass without use
* The user revokes the app at [https://app.box.com/account/security](https://app.box.com/account/security)
* The Box account password changes
* A new refresh token is issued (the old one expires after a short grace period)

The `BoxTokenManager` always uses the latest token. For long-running processes restart-safe,
provide `onRefreshTokenRotated` to persist the new token to disk / a vault.

## CORS Notes

Unlike Dropbox, Box has first-class CORS but **only for origins you've explicitly allowlisted**
in the **Allowed Origins** section of the app configuration. If you see browser-side
`Access-Control-Allow-Origin` errors, double-check the origin matches exactly (no trailing
slash; protocol matters).

## Troubleshooting

| Issue                                        | Fix                                                                                               |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `400 invalid_grant` on token exchange        | The `code` is single-use and short-lived, redo Step 5A with a fresh URL                           |
| `403 access_denied`                          | Enterprise admin hasn't approved the app on the **Authorization** tab                             |
| `401 unauthorized` after a few hours         | Refresh token rotation, the original token is invalid; persist via `onRefreshTokenRotated`        |
| Browser: `CORS error` even though origin set | Check that the origin in **Allowed Origins** is exact (no trailing slash, http vs https)          |
| `path/not_found` on `ls /box/<folder>/`      | The folder name was constructed wrong, use `ls /box/` first, then drill into a name from the list |

For TypeScript usage:

```ts theme={null}
import { BoxResource } from '@struktoai/mirage-node'

const box = new BoxResource({
  clientId: process.env.BOX_CLIENT_ID!,
  clientSecret: process.env.BOX_CLIENT_SECRET!,
  refreshToken: process.env.BOX_REFRESH_TOKEN!,
})
```

Or browser (PKCE):

```ts theme={null}
import { BoxResource } from '@struktoai/mirage-browser'

const box = new BoxResource({
  clientId: 'YOUR_CLIENT_ID',
  refreshToken: refreshTokenFromLocalStorage,
  onRefreshTokenRotated: (next) => localStorage.setItem('box-refresh', next),
})
```
