npm install @asciidev/box-sdk
BoxApi, configure auth, and call methods like box.create, box.prompt, and box.events.
Configure
import { BoxApi, Configuration, waitUntilReady, waitForPrompt } from "@asciidev/box-sdk";
const box = new BoxApi(new Configuration({
basePath: process.env.BOX_BASE_URL ?? "https://ascii.dev/api/box/v1",
accessToken: process.env.BOX_API_KEY ?? (() => {
throw new Error("Set BOX_API_KEY from the Box dashboard API keys tab.");
})(),
}));
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022", "DOM"],
"types": ["node"],
"strict": true
}
}
fetch. Older runtimes need a fetch polyfill. For ESM examples, set "type": "module" in package.json; CommonJS projects can use the require example below.
Create, prompt, and clean up
import { BoxApi, Configuration } from "@asciidev/box-sdk";
const box = new BoxApi(new Configuration({
basePath: process.env.BOX_BASE_URL ?? "https://ascii.dev/api/box/v1",
accessToken: process.env.BOX_API_KEY ?? (() => {
throw new Error("Set BOX_API_KEY from the Box dashboard API keys tab.");
})(),
}));
async function main() {
let boxId: string | undefined;
try {
const created = await box.create({
createBoxRequest: { ttlSeconds: 1800 },
});
boxId = created.box.id;
await box.update({
boxId,
updateBoxRequest: { name: "sdk-demo" },
});
await waitUntilReady(box, boxId);
const queued = await box.prompt({
boxId,
promptRequest: {
provider: "codex",
prompt: "Inspect the repository and summarize the test command.",
},
});
const run = await waitForPrompt(box, boxId, queued.promptId);
console.log(run.status);
const events = await box.events({ boxId, limit: 50, type: "prompt,response" });
console.log(events.events);
} finally {
if (boxId) await box.stop({ boxId });
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Use
ttlSeconds when creating a Box. Set a friendly name afterward with box.update.Machine size
Passtype to create a bigger Box. large consumes machine time at 2x, small at half rate. A
fork inherits the source Box’s type, and passing type to resume or fork moves a Box between
sizes.
const created = await box.create({
createBoxRequest: { type: "large", ttlSeconds: 3600 },
});
const info = await box.get({ boxId: created.box.id });
console.log(info.box.type, info.box.vcpu, info.box.memoryGB);
Per-box environment variables
Passenv to inject variables into a single Box, on top of your account-level secrets
(per-box values win on name conflicts). At most 100 variables, 64KB total.
const created = await box.create({
createBoxRequest: {
ttlSeconds: 3600,
env: { DATABASE_URL: "postgres://user:pass@host:5432/app", FEATURE_FLAG: "1" },
},
});
env unless the fork supplies its own.
No-env boxes
When you hand a Box to your own end users, create it withnoEnv: true so none of your
account secrets reach it. Model keys, GitHub token, dashboard environment variables, SSH
identity, and secret files are all withheld. SSH, desktop, snapshots, public URLs, and
forks still work. A fork of a no-env Box is always no-env and cannot be downgraded.
const created = await box.create({
createBoxRequest: { ttlSeconds: 1800, noEnv: true },
});
await box.resume({
boxId,
resumeRequest: { noEnv: true },
});
const forked = await box.fork({
boxId,
forkRequest: { noEnv: true },
});
CommonJS
const { BoxApi, Configuration } = require("@asciidev/box-sdk");
const box = new BoxApi(new Configuration({
basePath: process.env.BOX_BASE_URL || "https://ascii.dev/api/box/v1",
accessToken: process.env.BOX_API_KEY,
}));
Methods
All methods are called onBoxApi. Request bodies are plain objects typed by the exported model interfaces.
| Method | Arguments | Returns | Use |
|---|---|---|---|
me() | none | Promise<MeResponse> | Get the authenticated Box account user. |
limits() | none | Promise<LimitsResponse> | Check whether the account can create or operate Boxes before starting work. |
repos({ sync, limit, cursor, sort, q, selected }?) | optional sync, pagination, search, and selected-only filters | Promise<ReposResponse> | List GitHub installations, repositories, and selected repositories. |
selectRepo({ repoSelectionRequest }) | repositoryId, optional baseBranch | Promise<RepoSelectionResponse> | Select a repository for future Boxes. Use databaseId from repos() as repositoryId. |
apiKeys() | none | Promise<ApiKeysResponse> | List API key metadata, including 30-day request totals and live resource counts. Raw secrets are not returned. |
secrets() | none | Promise<SecretsResponse> | Read the current environment variables and secret files configured for Boxes. |
updateSecrets({ secretsUpdateRequest }) | envContents, secretFiles | Promise<SecretsResponse> | Replace the complete secret setup. Send every env var and file that should remain. |
boxes({ limit, cursor, sort, state }?) | optional pagination and state filter | Promise<BoxListResponse> | List Boxes for the account. |
create({ createBoxRequest }?) | optional type, ttlSeconds, env, environment, noEnv, setupScript, from, org | Promise<CreateBoxResponse> | Create a Box. Use ttlSeconds: null to disable auto-stop. Set noEnv: true to withhold all account secrets (for Boxes you give to your users). from starts the Box from a named snapshot; org bills it to an organization wallet. |
get({ boxId }) | Box id | Promise<BoxInfoResponse> | Fetch the latest Box state and connection fields. |
update({ boxId, updateBoxRequest }) | Box id plus name, ttlSeconds, and/or subdomain | Promise<BoxInfoResponse> | Rename a Box, change its auto-stop TTL, or rename its subdomain (re-points every live URL with no downtime). |
stop({ boxId, stopRequest }?) | Box id, optional force | Promise<BoxActionResponse> | Stop/archive a Box. A stop snapshots the disk first; if that is failing the stop is refused and the Box keeps running (you are not billed for that time). Pass stopRequest: { force: true } to stop anyway and permanently lose everything written since the last successful snapshot. |
resume({ boxId, resumeRequest }) | Box id, optional type, env, environment, ttlSeconds, noEnv | Promise<BoxActionResponse> | Resume an archived Box. Set resumeRequest: { noEnv: true } to convert it to no-env while scrubbing inherited owner secrets, or type to resume it onto a different machine size. ttlSeconds is omitted to keep the Box’s current auto-stop. Poll get() until it is ready. |
fork({ boxId, forkRequest }) | Box id, optional env, environment, type, ttlSeconds, noEnv | Promise<BoxActionResponse> | Create a new Box from the source Box snapshot. Set forkRequest: { noEnv: true } to create a no-env fork, or type to fork onto a different machine size. A fork does not inherit the source’s auto-stop: omit ttlSeconds and it gets the 1 hour default. The source Box is never modified. |
deleteBox({ boxId }) | Box id | Promise<BoxActionResponse> | Permanently delete a Box and its snapshots. Force-stops it, then deletes every snapshot chain only this Box uses. It cannot be resumed or forked afterwards and there is no undo, so use stop() if you want the data back later. Snapshot data a fork, a resume or a named snapshot still reads is kept. See Snapshots. |
prompt({ boxId, promptRequest }) | Box id plus provider, prompt, optional model, optional reasoningEffort | Promise<PromptResponse> | Queue work inside a Box. Returns promptRun.status and promptId. |
promptRunStatus({ boxId, promptId }) | Box id and prompt id | Promise<PromptRunResponse> | Read first-class prompt run status. |
events({ boxId, limit, cursor, sort, type }) | Box id plus optional pagination/filtering | Promise<EventsResponse> | Read typed event history for a Box. |
readFile({ boxId, path, encoding }) | Box id and relative path | Promise<FileReadResponse> | Deterministically read a text/base64 file from the Box work directory. |
writeFile({ boxId, fileWriteRequest }) | Box id plus relative path/content | Promise<FileWriteResponse> | Deterministically write a text/base64 file. |
command({ boxId, commandRequest }) | Box id plus command/cwd/timeout | Promise<CommandResponse> | Execute a bounded command in the Box work directory. |
artifact({ boxId, path }) | Box id and relative path | Promise<Blob> | Download an artifact as bytes. |
interrupt({ boxId }) | Box id | Promise<BoxActionResponse> | Interrupt current work in a running Box. |
desktop({ boxId, vnc, theme, requestBody }) | Box id plus optional desktop parameters. For VNC, send requestBody: { publicAccess: true } to return a URL without _token. | Promise<DesktopResponse> | Create or fetch a desktop streaming URL. Treat returned URLs as secrets. If provisioning is true, poll again. |
sshKey({ boxId, sshKeyRequest }) | Box id plus public SSH key | Promise<SshKeyResponse> | Add a public SSH key for Box SSH access. |
listSnapshots({ limit, cursor, sort }?) | optional pagination | Promise<SnapshotListResponse> | List completed snapshots across all Boxes; each item carries its boxId. |
listBoxSnapshots({ boxId, limit, cursor, sort }) | Box id plus optional pagination | Promise<SnapshotListResponse> | List completed snapshots for one Box. |
getLatestBoxSnapshot({ boxId }) | Box id | Promise<SnapshotLatestResponse> | Most recent completed snapshot for a Box, or null. |
getSnapshotTree({ snapshotId }) | Snapshot id | Promise<SnapshotTreeResponse> | Flat file/folder listing with sizes for a snapshot. Works with the Box stopped or archived. |
getSnapshotFile({ snapshotId, path }) | Snapshot id plus a path from the tree (empty for the whole snapshot) | Promise<Blob> | Download one file’s bytes, or a folder as a .tar archive, straight from the snapshot. Works with the Box stopped or archived. |
getSnapshotDownload({ snapshotId }) | Snapshot id | Promise<SnapshotDownloadResponse> | Signed chunk URLs to rebuild the snapshot’s full filesystem client-side. |
Browse a stopped Box’s filesystem
Snapshot reads never touch the machine, so they work while the Box is archived:const latest = await box.getLatestBoxSnapshot({ boxId });
const tree = await box.getSnapshotTree({ snapshotId: latest.snapshot.id });
const blob = await box.getSnapshotFile({ snapshotId: latest.snapshot.id, path: "projects/app/.env" });
Waiters and helpers
The package exports first-class waiters and deterministic file/command helper functions:import { waitUntilReady, waitUntilIdle, waitForDesktop, waitForPrompt, waitForPromptDone, streamEvents, streamPrompt, stopAndRemove, readText, writeText, execCommand } from "@asciidev/box-sdk";
await waitUntilReady(box, boxId);
const queued = await box.prompt({ boxId, promptRequest: { provider: "codex", prompt: "Run tests" } });
await waitForPrompt(box, boxId, queued.promptId);
const publicVnc = await waitForDesktop(box, boxId, { publicAccess: true });
await writeText(box, boxId, "notes/result.txt", "done\n");
const result = await execCommand(box, boxId, "cat notes/result.txt");
await stopAndRemove(box, boxId); // stop, keep the snapshots
await stopAndRemove(box, boxId, { delete: true }); // delete the Box and its snapshots
waitForPrompt/waitForPromptDone instead of inferring completion from box.state plus event polling. Use streamPrompt or streamEvents when you need incremental response/tool-call events as work runs.
Streaming responses and tool calls
The SDK now exportsstreamEvents and streamPrompt for response streaming. They use the Box v1 events cursor API under the hood, so no separate SSE or WebSocket endpoint is required. response events carry text in event.data.content; streaming partials set event.data.isStreaming; tool-call events are response events with event.data.tools.
import { BoxApi, Configuration, streamPrompt } from "@asciidev/box-sdk";
const box = new BoxApi(new Configuration({
basePath: "https://ascii.dev/api/box/v1",
accessToken: process.env.BOX_API_KEY!,
}));
const stream = streamPrompt(box, boxId, {
provider: "codex",
prompt: "Run pwd and ls, then summarize the result.",
});
for await (const event of stream) {
if (event.type !== "response") continue;
const data = event.data;
if (data.tools?.length) console.log("tools", data.tools);
if (data.content) process.stdout.write(data.content);
if (data.isStreaming) process.stdout.write("\n[partial]\n");
}
streamEvents(box, boxId, { type: "prompt,response" }) and stop it with an AbortController.
Operation request types
These exported interfaces wrap method parameters forBoxApi methods.
| Type | Fields | Used by |
|---|---|---|
ArtifactRequest | boxId, path | artifact() |
BoxesRequest | limit, cursor, sort, state | boxes() |
CommandOperationRequest | boxId, commandRequest | command() |
CreateRequest | createBoxRequest | create() |
DesktopRequest | boxId, vnc, theme, requestBody | desktop(); set requestBody.publicAccess for an ungated VNC URL. |
EventsRequest | boxId, limit, cursor, sort, type | events() |
ForkRequest | env, environment, type, ttlSeconds, noEnv | fork() |
GetRequest | boxId | get() |
InterruptRequest | boxId | interrupt() |
PromptOperationRequest | boxId, promptRequest | prompt() |
PromptRunStatusRequest | boxId, promptId | promptRunStatus() |
ReadFileRequest | boxId, path, encoding | readFile() |
RemoveRequest | boxId | remove() |
ReposRequest | sync, limit, cursor, sort, q, selected | repos() |
ResumeRequest | type, env, environment, ttlSeconds, noEnv | resume() |
SelectRepoRequest | repoSelectionRequest | selectRepo() |
SshKeyOperationRequest | boxId, sshKeyRequest | sshKey() |
StopRequest | boxId | stop() |
UpdateRequest | boxId, updateBoxRequest | update() |
UpdateSecretsRequest | secretsUpdateRequest | updateSecrets() |
WriteFileRequest | boxId, fileWriteRequest | writeFile() |
BoxesSortEnum | "asc", "desc" | boxes({ sort }) |
DesktopVncEnum | 1 | desktop({ vnc }) |
DesktopThemeEnum | "light", "dark" | desktop({ theme }) |
EventsSortEnum | "asc", "desc" | events({ sort }) |
ReadFileEncodingEnum | "utf8", "base64" | readFile({ encoding }) |
ReposSortEnum | "asc", "desc" | repos({ sort }) |
Model types
TypeScript models use camelCase fields.| Type | Fields | Notes |
|---|---|---|
ApiKey | id, name, keyPrefix, keyLastFour, createdAt, lastUsedAt, usage, resources | Metadata only; not the raw secret. Includes 30-day request total and live resource counts. |
ApiKeysResponse | ok, type, apiKeys | API key metadata response. Raw secrets are not returned. |
Box | id, name, state, type, vcpu, memoryGB, billingMultiplier, url, ip, createdAt, updatedAt, archiveAfter, desktopAvailable, desktopUrl, snapshotAvailable, snapshotCompletedAt | desktopUrl can contain a token; redact it. |
BoxActionResponse | ok, type, id, status, box | Returned by lifecycle actions such as stop, resume, fork, and interrupt. |
BoxInfoResponse | ok, type, box | Returned by get() and update(). |
BoxEvent | id, type, timestamp, taskId, data, plus additional fields | Extensible event object returned inside EventsResponse.events. Branch on each event type. |
BoxListResponse | ok, type, boxes, pageInfo | Returned by boxes(). |
CommandRequest | command, cwd, timeoutSeconds | Bounded command execution request. |
CommandResponse | ok, type, success, exitCode, signal, stdout, stderr, stdoutTruncated, stderrTruncated, timedOut, cwd, startedAt, finishedAt | Returned by command() and execCommand(). |
CompletionEvent | id, type, timestamp, taskId, data | Event subtype for task_notification and compaction_complete. |
CreateBoxRequest | type, ttlSeconds, env, environment, noEnv, setupScript, from, org | type is small, default or large (see Machine size). ttlSeconds is the delay before auto-stop; null disables it. from names a snapshot to start from; org bills the Box to an organization wallet. |
CreateBoxResponse | ok, type, status, ttlSeconds, box | Returned immediately after creation starts. |
DesktopResponse | ok, type, success, desktopUrl, ip, mode, provisioning, message | If provisioning is true, poll desktop() again. |
ErrorEnvelope | ok, type, status, code, message, requestId, error | Non-2xx response body. Include requestId in support logs. |
ErrorEnvelopeError | code, message, status, details | Structured error details. |
ErrorEvent | id, type, timestamp, taskId, data | Event subtype for usage_limit and shield. |
EventsResponse | ok, type, id, events, pageInfo | events contains Box event objects. |
FileReadResponse | ok, type, success, path, encoding, size, content | Returned by readFile() and readText(). |
FileWriteRequest | path, content, encoding | Write a UTF-8 string or base64 payload. |
FileWriteResponse | ok, type, success, path, encoding, size | Returned by writeFile() and writeText(). |
LimitsFields | accessTier, blockedReason, currentLimits, standardLimits, trialLimits, upgradeEffects, canStart, checkoutRequired, startBlockedReason, contactMessage, activeBoxes, activeStates, maxActiveBoxes, maxCreationRequestsPerMinute, maxCreationRequestsPerDay, startLimits, starts, accountPlan, plan, planName, serviceAccount, unlimited, hasPaymentHistory, _package, subscriptionQuotaSeconds, subscriptionRemainingSeconds, packBalanceSeconds, packBalanceHours, packBalanceDollars, creditPurchasedSeconds, creditUsedSeconds, liveUsageSeconds, creditSecondsPerDollar, billingStatus, subscriptionStatus, subscriptionCancelAtPeriodEnd, hasSubscription, subscriptionTrialEndsAt, subscriptionCurrentPeriodEnd, creditBalanceSeconds, creditBalanceHours | Shared limit and billing-access fields. Use canStart and startBlockedReason before creating Boxes. starts.*.remaining is remaining machine starts in each rolling window. serviceAccount/unlimited identify admin-created automation accounts. |
LimitsFieldsCurrentLimits | activeBoxes, creationRatePerMinute, creationRequestsPerDay | Numeric quota limits. |
LimitsResponse | ok, type, plus all LimitsFields fields | Returned by limits(). |
MeResponse | ok, type, user | Authenticated account response. |
MeResponseAllOfUser | login, email | User identity fields. |
PageInfo | nextCursor, hasMore, limit | Optional pagination metadata on list responses. |
PromptEvent | id, type, timestamp, taskId, data | Event subtype for prompt. |
PromptEventData | prompt, status, isReverted | Prompt event payload. |
PromptRequest | provider, model, reasoningEffort, prompt | provider is codex or claude-code. Omit model to use the saved default. |
PromptResponse | ok, type, id, promptId, promptRun, status, provider, model, reasoningEffort | Returned after work is queued. |
PromptRun | id, promptId, boxId, status, done, createdAt, model, reasoningEffort | First-class prompt run state. |
PromptRunResponse | ok, type, id, promptRun | Returned by promptRunStatus(). |
ResponseEvent | id, type, timestamp, taskId, data | Event subtype for response. |
ResponseEventData | content, model, tools, isStreaming | Response event payload. |
RepoSelectionRequest | repositoryId, baseBranch | repositoryId is a databaseId from repos(). |
RepoSelectionResponse | ok, type, success, environmentId, selectedRepositories | Returned by selectRepo(). |
ReposResponse | ok, type, installations, environmentId, selectedRepositories, pageInfo | Repository inventory and current selections. |
Repository | id, databaseId, name, fullName, _private, permissions, pushedAt | Use databaseId when selecting a repository. |
RepositoryInstallation | type, accountLogin, accountAvatarUrl, repositories | Group of repositories available through one installation/account. |
SecretFile | path, contents | Treat contents as sensitive. |
SecretsResponse | ok, type, success, environmentId, envContents, secretFiles, pushed | Current secret setup. Treat envContents and secretFiles as sensitive. |
SecretsUpdateRequest | envContents, secretFiles | Full replacement request for secrets. |
SelectedRepository | id, databaseId, name, fullName, _private, permissions, pushedAt, baseBranch, setupRoutineId, setupScript, setupBlocking | Repository selected for future Boxes. |
SshKeyRequest | key | Public SSH key in OpenSSH format. |
SshKeyResponse | ok, type, success, machineIp, sshUser | Returned after adding an SSH key. |
SuccessBase | ok, type | Base success-envelope fields. |
UnknownEvent | type, plus additional properties | Forward-compatible fallback for event types the SDK does not model yet. |
UpdateBoxRequest | name, ttlSeconds | Send only fields you want to change. ttlSeconds: null disables auto-stop. |
Errors
Non-2xx responses reject with aResponseError. Read the status and parse the JSON body for the structured Box error envelope. Redact API keys, Box secrets, SSH keys, and desktop URLs.
import { ResponseError } from "@asciidev/box-sdk";
try {
await box.get({ boxId: "bx_missing" });
} catch (error) {
if (error instanceof ResponseError) {
console.error(error.response.status);
console.error(await error.response.json());
}
}