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,git_checkpoint" });
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.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. 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 ttlSeconds, noEnv | 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). |
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 }) | Box id | Promise<BoxActionResponse> | Stop/archive a Box. |
resume({ boxId, resumeRequest }) | Box id, optional noEnv | Promise<BoxActionResponse> | Resume an archived Box. Set resumeRequest: { noEnv: true } to convert it to no-env while scrubbing inherited owner secrets. Poll get() until it is ready. |
fork({ boxId, forkRequest }) | Box id, optional env, optional noEnv | Promise<BoxActionResponse> | Create a new Box from the source Box snapshot. Set forkRequest: { noEnv: true } to create a no-env fork. |
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);
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,git_checkpoint" }) 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, 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 | 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 | Metadata only; not the raw secret. |
ApiKeysResponse | ok, type, apiKeys | API key metadata response. Raw secrets are not returned. |
Box | id, name, state, 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 | ttlSeconds | Seconds before auto-stop; null disables auto-stop. |
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(). |
GitCheckpointEvent | id, type, timestamp, taskId, data | Event subtype for git_checkpoint. |
GitCheckpointEventData | commitSha, commitMessage, commitUrl, branch, filesChanged, additions, deletions, pushed | Git checkpoint event payload. |
LimitsFields | accessTier, blockedReason, currentLimits, standardLimits, trialLimits, upgradeEffects, canStart, checkoutRequired, startBlockedReason, contactMessage, activeBoxes, activeStates, maxActiveBoxes, maxCreationRequestsPerMinute, maxCreationRequestsPerDay, accountPlan, plan, planName, serviceAccount, unlimited, hasPaymentHistory, _package, subscriptionQuotaSeconds, subscriptionRemainingSeconds, packBalanceSeconds, creditPurchasedSeconds, creditUsedSeconds, liveUsageSeconds, creditSecondsPerDollar, billingStatus, subscriptionStatus, subscriptionCancelAtPeriodEnd, hasSubscription, subscriptionTrialEndsAt, subscriptionCurrentPeriodEnd, creditBalanceSeconds | Shared limit and billing-access fields. Use canStart and startBlockedReason before creating Boxes. 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, preCommitHooks | Repository selected for future Boxes. |
SelectedRepositoryAllOfPreCommitHooks | id, script, blocking | Pre-commit hook configured for a selected repository. |
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());
}
}