# Agent Drive overview
Source: https://docs.blaxel.ai/Agent-drive/Overview
Agent Drive is a distributed filesystem mountable to multiple sandboxes or agents simultaneously, with concurrent read-write access and built-in replication.
This feature is currently in private preview. During the preview, the Agent Drive feature is only available in the `us-was-1` region. Both your drive and your sandbox must be created in this region. Drive size is not configurable at the moment. A quotas system for drive storage is coming soon. [Request access](https://blaxel.fillout.com/t/pbTXmanx3Sus).
Agent Drive is a distributed filesystem that can be mounted to multiple sandboxes or agents at any time, including while they are already running. Drives support concurrent read-write access (RWX) from multiple sandboxes simultaneously, with built-in replication for durability.
Unlike [volumes](/Volumes/Overview), which are block storage devices attached at sandbox creation to a single sandbox, drives behave like a shared cloud filesystem, but mounted directly into a sandbox's file tree. An optimized FUSE client built specifically for this filesystem is added directly to the sandbox or agent to give a POSIX-compliant interface.
* A drive can be attached to an already-running sandbox at any mount path, without needing to recreate the sandbox.
* Multiple sandboxes can mount the same drive simultaneously with full read-write access.
* A specific subdirectory of a drive can be mounted using `drivePath` (instead of mounting the entire drive).
* Drives scale automatically with no fixed capacity limits. Pre-provisioning or run-time resizing is not required.
Refer to our [blog post on choosing the right storage for your Blaxel agents](https://blaxel.ai/blog/choose-the-right-storage-for-your-blaxel-agents) for more information.
## Use cases
Some examples of use cases are:
* Passing data or files from one sandbox to another directly, without needing intermediary storage or services
* Storing tool call outputs and context histories for use in other agents
* Sharing common datasets across agents
* Creating a shared filesystem cache of package dependencies to speed up future agent/sandbox deployments
## Create a drive
The Blaxel SDK requires two environment variables to authenticate:
| Variable | Description |
| - | - |
| `BL_WORKSPACE` | Your Blaxel workspace name |
| `BL_API_KEY` | Your Blaxel API key |
You can create an API key from the [Blaxel console](https://app.blaxel.ai/profile/security). Your workspace name is visible in the URL when you log in to the console (e.g. `app.blaxel.ai/{workspace}`).
Set them as environment variables or add them to a `.env` file at the root of your project:
```bash theme={null}
export BL_WORKSPACE=my-workspace
export BL_API_KEY=my-api-key
```
When developing locally, you can also **log in to your workspace with Blaxel CLI** (as shown above). This allows you to run Blaxel SDK functions that will automatically connect to your workspace without additional setup. When you deploy on Blaxel, authentication is handled automatically — no environment variables needed.
Create a standalone drive by specifying a unique `name` and `region`. You can also optionally specify the display name and labels for the drive.
```tsx TypeScript theme={null}
import { DriveInstance } from "@blaxel/core";
const drive = await DriveInstance.create({
name: "my-drive",
region: "us-was-1",
displayName: "My Project Drive", // optional; defaults to `name`
labels: { env: "dev", project: "my-project" }, // optional; labels
});
```
```python Python theme={null}
from blaxel.core.drive import DriveInstance
drive = await DriveInstance.create(
{
"name": "my-drive",
"region": "us-was-1",
"display_name": "My Project Drive", # optional; defaults to `name`
"labels": {"env": "dev", "project": "my-project"}, # optional; labels
}
)
```
```bash CLI theme={null}
bl drive create --name my-drive --region us-was-1
```
You can also use `createIfNotExists()` to retrieve an existing drive or create a new one if it doesn't exist:
```tsx TypeScript theme={null}
const drive = await DriveInstance.createIfNotExists({
name: "my-drive",
region: "us-was-1",
displayName: "My Project Drive", // optional; defaults to `name`
labels: { env: "dev", project: "my-project" }, // optional; labels
});
```
```python Python theme={null}
from blaxel.core.drive import DriveInstance
drive = await DriveInstance.create_if_not_exists(
{
"name": "my-drive",
"region": "us-was-1",
"display_name": "My Project Drive", # optional; defaults to `name`
"labels": {"env": "dev", "project": "my-project"}, # optional; labels
}
)
```
## Mount a drive to a sandbox
Drives cannot be attached in the sandbox create call. Unlike [volumes](/Sandboxes/Volumes#attach-a-volume-to-a-sandbox), which use `volumes: [{ name, mountPath }]` at creation, drives are mounted afterward. Create or get a drive in the same region as your sandbox, then call `sandbox.drives.mount(...)`.
Specify the `driveName`, the `mountPath` (where the drive will appear in the sandbox's filesystem), and optionally the `drivePath` (a subdirectory within the drive to mount).
```tsx TypeScript theme={null}
import { SandboxInstance, DriveInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.createIfNotExists({
name: "my-app",
region: "us-was-1",
});
const drive = await DriveInstance.createIfNotExists({
name: "my-app-data",
region: "us-was-1",
});
await sandbox.drives.mount({
driveName: drive.name,
mountPath: "/mnt/data",
drivePath: "/", // optional; defaults to root of the drive
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
from blaxel.core.drive import DriveInstance
sandbox = await SandboxInstance.create_if_not_exists({
"name": "my-app",
"region": "us-was-1",
})
drive = await DriveInstance.create_if_not_exists({
"name": "my-app-data",
"region": "us-was-1",
})
await sandbox.drives.mount(
drive_name=drive.name,
mount_path="/mnt/data",
drive_path="/", # optional; defaults to root of the drive
)
```
```bash CLI theme={null}
bl drive mount --sandbox my-app --drive my-app-data --mount-path /mnt/data
```
Once mounted, any file written to `/mnt/data` inside the sandbox will be stored on the drive and persist even after the sandbox is deleted.
### Mount a subdirectory
You can mount a specific subdirectory of a drive rather than its root. This is useful when a single drive contains multiple project directories:
```tsx TypeScript theme={null}
await sandbox.drives.mount({
driveName: "my-drive",
mountPath: "/app/project",
drivePath: "/projects/alpha",
});
```
```python Python theme={null}
await sandbox.drives.mount(
drive_name="my-drive",
mount_path="/app/project",
drive_path="/projects/alpha",
)
```
```bash CLI theme={null}
bl drive mount --sandbox my-sandbox --drive my-drive --mount-path /app/project --drive-path /projects/alpha
```
`drivePath` / `drive_path` controls the subtree visible to the sandbox. When combined with [drive permissions](/Agent-drive/Permissions), you can restrict a workload to a specific subfolder of a drive using the `path` field in a permission rule.
### Mount a drive as read-only
You can mount a drive in read-only mode:
```tsx TypeScript theme={null}
await sandbox.drives.mount({
driveName: "my-drive",
mountPath: "/mnt/shared",
readOnly: true,
});
```
```python Python theme={null}
await sandbox.drives.mount(
drive_name="my-drive",
mount_path="/mnt/shared",
read_only=True,
)
```
```bash CLI theme={null}
bl drive mount --sandbox my-sandbox --drive my-drive --mount-path /mnt/shared --read-only
```
The same drive can be mounted read-write in one sandbox and read-only in others. However, any write attempt to a drive mounted as read-only will fail with a permission error.
When [drive permissions](/Agent-drive/Permissions) are configured with `mode: "read"`, read-only access is enforced both at the FUSE mount level and at the storage server level. The workload's identity token is validated against the permission rules, preventing unauthorized remounting in read-write mode.
## List mounted drives
List all drives currently mounted to a sandbox:
```tsx TypeScript theme={null}
const mounts = await sandbox.drives.list();
console.log(mounts);
```
```python Python theme={null}
mounts = await sandbox.drives.list()
print(mounts)
```
```bash CLI theme={null}
bl drive mounts --sandbox my-sandbox
```
## List all drives
To retrieve all drives in your workspace, use the built-in SDK pagination helper functions.
Pagination is recommended for list operations because retrieving all available resources in a single request is slow and resource-intensive, and therefore does not scale well.
```tsx TypeScript theme={null}
import { DriveInstance } from "@blaxel/core";
const drives = await DriveInstance.list({ limit: 50 });
for await (const drive of drives) {
console.log(drive.name);
}
```
```python Python theme={null}
import asyncio
from blaxel.core.drive import DriveInstance
async def main():
drives = await DriveInstance.list(limit=50)
async for drive in drives.auto_paging_iter():
print(drive.name)
asyncio.run(main())
```
```bash CLI theme={null}
bl drive list
```
For more information on the SDK pagination helpers, refer to the [SDK reference documentation](/sdk-reference/introduction#pagination).
Pagination is also available via the Management API list endpoint.
Request the first page:
```bash theme={null}
curl 'https://api.blaxel.ai/v0/drives?limit=50' \
-H 'X-Blaxel-Authorization: Bearer YOUR-API-KEY' \
-H 'Blaxel-Version: 2026-04-28'
```
The response includes a `data` array and a `meta` object:
```json theme={null}
{
"data": [ ... ],
"meta": {
"hasMore": true,
"nextCursor": "...",
"total": 24
}
}
```
Pass `meta.nextCursor` as `cursor` on the next request and repeat until `meta.hasMore` is `false`:
```bash theme={null}
curl 'https://api.blaxel.ai/v0/drives?limit=50&cursor=NEXT_CURSOR' \
-H 'X-Blaxel-Authorization: Bearer YOUR-API-KEY' \
-H 'Blaxel-Version: 2026-04-28'
```
For accounts with multiple workspaces, specify the workspace as an additional request parameter, such as `?workspace=WORKSPACE_NAME&...`, or via an additional `X-Blaxel-Workspace: WORKSPACE_NAME` header in the request.
For more information, refer to the [API reference documentation](/api-reference/introduction#pagination).
## Get drive details
Retrieve details about a specific drive:
```bash CLI theme={null}
bl drive get my-drive
```
## Unmount a drive
Unmount a drive from a running sandbox by specifying the mount path:
```tsx TypeScript theme={null}
await sandbox.drives.unmount("/mnt/data");
```
```python Python theme={null}
await sandbox.drives.unmount("/mnt/data")
```
```bash CLI theme={null}
bl drive unmount --sandbox my-sandbox --mount-path /mnt/data
```
## Delete a drive
```tsx TypeScript theme={null}
import { DriveInstance } from "@blaxel/core";
// Class-level
await DriveInstance.delete("my-drive");
// Or instance-level
const drive = await DriveInstance.get("my-drive");
await drive.delete();
```
```python Python theme={null}
from blaxel.core.drive import DriveInstance
# Class-level
await DriveInstance.delete("my-drive")
# Or instance-level
drive = await DriveInstance.get("my-drive")
await drive.delete()
```
```bash CLI theme={null}
bl drive delete my-drive
```
Complete code examples demonstrating all operations are available in Blaxel's GitHub repositories, [for TypeScript](https://github.com/blaxel-ai/sdk-typescript/tree/main/tests/integration/sandbox), [for Python](https://github.com/blaxel-ai/sdk-python/tree/main/tests/integration/core/sandbox), and [for Go](https://github.com/blaxel-ai/sdk-go/tree/main/integration_tests).
## Full example
The following example creates a drive, creates a sandbox from a custom sandbox image using its image ID, mounts the drive, writes a file to the mounted path, and reads it back:
```tsx TypeScript theme={null}
import { SandboxInstance, DriveInstance } from "@blaxel/core";
// 1. Create a drive
const drive = await DriveInstance.createIfNotExists({
name: "agent-storage",
region: "us-was-1",
});
// 2. Create a sandbox
// Use the image ID of the custom sandbox image
const sandbox = await SandboxInstance.createIfNotExists({
name: "my-agent-sandbox",
image: "my-sandbox-image-id",
memory: 2048,
region: "us-was-1",
});
// 3. Mount the drive to the sandbox
await sandbox.drives.mount({
driveName: "agent-storage",
mountPath: "/mnt/storage",
drivePath: "/",
});
// 4. Write a file to the mounted drive
await sandbox.fs.write("/mnt/storage/hello.txt", "Hello from the drive!");
// 5. Read it back
const content = await sandbox.fs.read("/mnt/storage/hello.txt");
console.log(content); // "Hello from the drive!"
// 6. List mounted drives
const mounts = await sandbox.drives.list();
console.log(mounts);
```
```python Python theme={null}
import asyncio
from blaxel.core.drive import DriveInstance
from blaxel.core import SandboxInstance
async def main():
# 1. Create a drive
drive = await DriveInstance.create_if_not_exists(
{
"name": "agent-storage",
"region": "us-was-1",
}
)
# 2. Create a sandbox
sandbox = await SandboxInstance.create_if_not_exists(
{
"name": "my-agent-sandbox",
"image": "my-sandbox-image-id",
"memory": 2048,
"region": "us-was-1",
}
)
# 3. Mount the drive to the sandbox
await sandbox.drives.mount(
drive_name="agent-storage",
mount_path="/mnt/storage",
drive_path="/",
)
# 4. Write a file to the mounted drive
await sandbox.fs.write("/mnt/storage/hello.txt", "Hello from the drive!")
# 5. Read it back
content = await sandbox.fs.read("/mnt/storage/hello.txt")
print(content) # "Hello from the drive!"
# 6. List mounted drives
mounts = await sandbox.drives.list()
print(mounts)
asyncio.run(main())
```
Restrict which sandboxes, agents, or jobs can access a drive.
Learn about sandbox lifecycle and configuration.
# Drive permissions
Source: https://docs.blaxel.ai/Agent-drive/Permissions
Configure label-based ACL rules on an Agent Drive to grant sandboxes, agents, or jobs read or read-write access, scoped to specific paths.
Drive permissions control which sandboxes, agents, or jobs can access the contents of a drive (or a specific folder in a drive). They are access control rules set at the drive level, evaluated against the workload's identity labels. If no permissions are defined on a drive, any workload in the workspace can access all of its contents.
## Mount and access are different things
The most common point of confusion is treating a mount as an access boundary. It is not. Mounting and accessing a drive are two separate operations, and permissions govern access, not the mount.
* Mounting is a client-side operation. It projects a drive's contents into a sandbox's filesystem at a mount path, so the workload can read and write files with a standard POSIX interface. Mounting a drive is like serving a website locally: it changes where the content appears, not who is allowed to reach it.
* Accessing is reaching the drive's contents. Agent Drive exposes an internal HTTP API, so a workload can read and write a drive's files directly, whether or not the drive is mounted anywhere.
Because a workload can reach a drive's contents through the API without ever mounting it, controlling the mount does not isolate a drive. Permissions are what isolate a drive: they are enforced by the server for every access path, both mounts and direct API calls.
By default, a new drive has no permissions, which means any workload in the workspace can access all of its contents, mounted or not. Set permissions to restrict access to the workloads you choose.
## How permissions work
Permissions are a list of access control rules attached to a drive. A workload is granted access when it matches a rule, and the same rules apply whether the workload mounts the drive or calls the HTTP API directly.
Each drive can have up to 3 permission rules. A permission rule contains:
| Field | Description | Default |
| - | - | - |
| `labels` | Key-value pairs the workload must have (AND logic within a rule) | Required |
| `mode` | Access mode: `read` or `read-write` | `read-write` |
| `path` | Subfolder the workload is restricted to | `/` (full drive) |
Rules are evaluated with OR logic: the first matching rule grants access with its `mode` and `path`. Within a single rule, all specified labels must match (AND logic). If no rule matches, access is denied.
## Label matching
Workload labels are automatically injected into the workload's identity token at creation time. These include infrastructure labels (like `blaxel-workspace`, `blaxel-type`, `blaxel-name`) and any user-defined labels set on the resource's metadata.
To add user-defined labels to a sandbox, set them in `labels` when creating the resource:
```tsx TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.create({
name: "my-sandbox",
image: "my-image",
labels: { team: "backend", env: "production" },
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.create(
{
"name": "my-sandbox",
"image": "my-image",
"labels": {"team": "backend", "env": "production"},
}
)
```
These labels then appear in the workload identity token and are evaluated against the drive's permission rules on every access.
## Create a drive with permissions
```tsx TypeScript theme={null}
import { DriveInstance } from "@blaxel/core";
const drive = await DriveInstance.create({
name: "team-drive",
region: "us-was-1",
permissions: [
{
labels: { team: "backend" },
mode: "read-write",
},
{
labels: { team: "frontend" },
mode: "read",
path: "/shared",
},
],
});
```
```python Python theme={null}
from blaxel.core.drive import DriveInstance
drive = await DriveInstance.create(
{
"name": "team-drive",
"region": "us-was-1",
"permissions": [
{
"labels": {"team": "backend"},
"mode": "read-write",
},
{
"labels": {"team": "frontend"},
"mode": "read",
"path": "/shared",
},
],
}
)
```
## Update permissions on an existing drive
Permissions can be modified on a drive that is already in use.
```tsx TypeScript theme={null}
import { DriveInstance } from "@blaxel/core";
const drive = await DriveInstance.get("team-drive");
await drive.update({
permissions: [
{
labels: { team: "backend", env: "production" },
mode: "read-write",
},
],
});
```
```python Python theme={null}
from blaxel.core.drive import DriveInstance
drive = await DriveInstance.get("team-drive")
await drive.update(
{
"permissions": [
{
"labels": {"team": "backend", "env": "production"},
"mode": "read-write",
},
],
}
)
```
To remove all permissions and make the drive open-access again, set `permissions` to an empty array.
## Permission patterns
### Restrict to a single team
Only workloads with `team: "data-science"` can access the drive:
```tsx TypeScript theme={null}
permissions: [
{ labels: { team: "data-science" }, mode: "read-write" },
]
```
### AND logic (multiple labels in one rule)
The workload must have both `team: "backend"` AND `env: "production"` to match:
```tsx TypeScript theme={null}
permissions: [
{ labels: { team: "backend", env: "production" }, mode: "read-write" },
]
```
### OR logic (multiple rules)
Either `team: "backend"` OR `team: "ml"` can access the drive:
```tsx TypeScript theme={null}
permissions: [
{ labels: { team: "backend" }, mode: "read-write" },
{ labels: { team: "ml" }, mode: "read-write" },
]
```
### Read-only access for some teams
The backend team gets full access, the frontend team can only read:
```tsx TypeScript theme={null}
permissions: [
{ labels: { team: "backend" }, mode: "read-write" },
{ labels: { team: "frontend" }, mode: "read" },
]
```
### Path scoping
Restrict a workload to a specific subfolder within the drive:
```tsx TypeScript theme={null}
permissions: [
{ labels: { team: "analytics" }, mode: "read", path: "/reports" },
]
```
The workload can only access files under `/reports`, whether it reads them through the API or mounts the drive.
## Behavior summary
| Scenario | Result |
| - | - |
| No permissions defined on drive | Any workload in the workspace can access all contents (via mount or internal API) |
| Permissions defined, workload labels match a rule | Access allowed with the rule's mode and path |
| Permissions defined, no rule matches | Access denied |
| Multiple rules match | First matching rule applies |
| Rule with empty `mode` | Defaults to `read-write` |
| Rule with empty `path` | Defaults to `/` (full drive) |
Create, mount, and manage drives.
Learn about sandbox lifecycle and configuration.
# Access Agent Drive over S3
Source: https://docs.blaxel.ai/Agent-drive/S3-endpoint
Access an Agent Drive instance from S3-compatible clients using its HTTP endpoint, service account credentials, and SigV4 signing.
Every drive exposes an S3-compatible HTTP endpoint, so you can read and write its contents directly from any S3 client, such as the AWS CLI, boto3, or the AWS SDK for JavaScript, without mounting the drive into a sandbox first.
Requests must use SigV4 signing and path-style addressing (the bucket in the path, not the hostname). Credentials are a [service account](/Security/Service-accounts)'s [API key](/Security/Access-tokens): `AWS_ACCESS_KEY_ID` is the API key's record ID and `AWS_SECRET_ACCESS_KEY` is its raw secret value — **not** the service account's OAuth client ID/secret pair. Personal API keys and OAuth tokens are not supported for S3 access.
Drive permission rules (labels, `path`, `mode`) are **not currently enforced** for S3 access. A valid service-account API key grants full read-write access to every drive in the workspace over S3, regardless of any [drive permissions](/Agent-drive/Permissions) configured on them. Only issue S3 credentials to service accounts you're comfortable giving full access to all drives in the workspace; enforcing permission rules over S3 is planned but not yet available.
API keys created before S3 support was added may not work for SigV4 signing and will return `InvalidAccessKeyId`. If you hit this error, create a new API key for the service account (or rotate the existing one) and use the new credentials.
## Find a drive's S3 endpoint and bucket
The endpoint is returned in the drive's state as `s3Url`, in the form `{endpoint}/{bucket}`:
```tsx TypeScript theme={null}
import { DriveInstance } from "@blaxel/core";
const drive = await DriveInstance.get("my-drive");
console.log(drive.state?.s3Url);
```
```python Python theme={null}
from blaxel.core.drive import DriveInstance
drive = await DriveInstance.get("my-drive")
state = drive.drive.state
print(state.s_3_url if state and state.s_3_url else None) # low-level field name; a higher-level property is planned
```
The bucket is the last path segment of `s3Url`; the rest of the URL is the endpoint to pass to your S3 client.
The Blaxel CLI does not currently expose the S3 endpoint via `bl drive get`. Use the TypeScript or Python SDK to retrieve it until CLI support is added.
## Use the AWS CLI
Export the service account's API key as AWS-style credentials, then use standard `aws s3` commands with `--endpoint-url` and `--region` set to the drive's endpoint and region:
```bash theme={null}
export AWS_ACCESS_KEY_ID="" # the API key's record ID, not the service account's OAuth client ID
export AWS_SECRET_ACCESS_KEY="" # the API key's raw secret value, not the OAuth client secret
```
```bash theme={null}
aws s3 ls "s3:///" \
--endpoint-url "" \
--region ""
```
```bash theme={null}
aws s3 cp ./local.bin "s3:///path/local.bin" \
--endpoint-url "" --region ""
aws s3 cp "s3:///path/local.bin" ./local.bin \
--endpoint-url "" --region ""
```
The region passed to the S3 client must match the drive's `region`, and the endpoint must be called with path-style addressing (the default for most S3 clients when a custom endpoint is set).
# Applications
Source: https://docs.blaxel.ai/Applications/Overview
Serve AI-generated code as publicly accessible endpoints with revision management and custom URLs.
This feature is currently in private preview and is not recommended for production use.
Applications are long-running, deployed services that run custom code as publicly accessible endpoints. They are ideal for serving AI-generated software to end-users. They support revision management, [custom domain URLs](/Infrastructure/Custom-domains), and deployment from source code, images, or [forked sandboxes](/Sandboxes/Fork).
## Design philosophy
Autonomous agents now all have coding abilities. It gives them near-infinite flexibility for tool calling. We believe the future of autonomous agents goes one step further: they will build ad hoc user-facing software to expose as micro-apps to end-users - whether humans-in-the-loop, or other agents.
As AI agents produce outputs faster than humans by several orders of magnitude, a key requirement is the ability for agents to iterate on this user-facing software in near real-time. For instance, in a world where an agent generates a full running app in a couple of minutes, pushes it to prod, and iterates on it every few minutes, it cannot afford a build time for every deployment.
Conceptually, an Application is a snapshot of an AI-generated piece of software, frozen at a point in time and served to end users. Sandboxes, on the other hand, are the development & experimentation environment for agents. In a sandbox, they can do anything anytime, but that also means they can break anything which becomes a problem once it’s serving live traffic.
With that framing, features can be built specifically for each use case, for example:
* A distinct custom domain per app. Each Application typically belongs to a different end user, whereas a Sandbox usually sits behind a single custom domain: yours.
* A real deployment lifecycle: blue-green deploys, rollbacks, observability, etc.
Blaxel aims to power both the development phase (Blaxel Sandboxes) and the production phase (Blaxel Applications) of AI-generated software: you can flip from dev to prod in a few milliseconds thanks to the core Blaxel runtime.
## Key concepts
* **Application**: A long-running deployment that serves traffic on a public URL.
* **Revision**: An immutable snapshot of the application code, image, environment, and memory configuration. Each deploy creates a new revision (max 5 kept).
* **Custom URLs**: Map verified custom domains to your application.
## Create an application
### Using the SDKs
```typescript TypeScript theme={null}
import { ApplicationInstance } from "@blaxel/core";
const app = await ApplicationInstance.create({
name: "my-app",
image: "app/my-image:latest",
memory: 2048,
region: "us-pdx-1",
envs: [{ name: "NODE_ENV", value: "production" }],
});
```
```python Python theme={null}
from blaxel.core import ApplicationInstance
from blaxel.core.client.models import Application, ApplicationSpec, Metadata, Env
app = await ApplicationInstance.create(Application(
metadata=Metadata(name="my-app"),
spec=ApplicationSpec(
enabled=True,
region="us-pdx-1",
image="app/my-image:latest",
memory=2048,
envs=[Env(name="NODE_ENV", value="production")],
),
))
```
```go Go theme={null}
import (
blaxel "github.com/blaxel-ai/sdk-go"
"github.com/blaxel-ai/sdk-go/shared"
)
app, err := client.Applications.New(ctx, blaxel.ApplicationNewParams{
Application: blaxel.ApplicationParam{
Metadata: blaxel.MetadataParam{Name: "my-app"},
Spec: blaxel.ApplicationSpecParam{
Enabled: blaxel.Bool(true),
Region: blaxel.String("us-pdx-1"),
Image: blaxel.String("app/my-image:latest"),
Memory: blaxel.Int(2048),
Envs: []shared.EnvParam{{
Name: blaxel.String("NODE_ENV"),
Value: blaxel.String("production"),
}},
},
},
})
```
### Using the CLI
Deploy from a project directory containing a `blaxel.toml`:
```bash theme={null}
bl deploy --type application
```
Example `blaxel.toml`:
```toml theme={null}
name = "my-app"
type = "application"
workspace = "my-workspace"
[env]
NODE_ENV = "production"
[deploy]
region = "us-pdx-1"
memory = 2048
```
To reuse an image already built on Blaxel (from a previous `bl deploy` or `bl push`), set the `image` field. External registries are not supported — the image must live in your workspace's Blaxel registry:
```toml theme={null}
name = "my-app"
type = "application"
workspace = "my-workspace"
image = "app/my-image:latest"
```
### Using the HTTP API
```bash theme={null}
curl -X POST https://api.blaxel.ai/v0/applications \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d '{
"metadata": { "name": "my-app" },
"spec": {
"enabled": true,
"region": "us-pdx-1",
"image": "app/my-image:latest",
"memory": 2048,
"envs": [{ "name": "NODE_ENV", "value": "production" }]
}
}'
```
## Create an application from a sandbox fork
You can turn a running sandbox into an application by [forking it](/Sandboxes/Fork). The application's `image`, `memory`, `envs`, and `port` are inherited from the source sandbox, so the fork starts serving the sandbox's current state on a stable application URL with revision management and custom domains on top.
This is useful for promoting a sandbox you developed and tested interactively into a long-running, publicly addressable service without rebuilding an image.
Fork a sandbox into an application with `sandbox.fork(targetName, { targetType: "application" })`:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
// Fork the sandbox into an application named "my-app".
const result = await sandbox.fork("my-app", {
targetType: "application",
});
console.log(result.type); // "application"
console.log(result.name); // "my-app"
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
# Fork the sandbox into an application named "my-app".
result = await sandbox.fork("my-app", target_type="application")
print(result.type) # "application"
print(result.name) # "my-app"
```
The fork snapshots the sandbox's live state automatically.
### Fork from an earlier snapshot
To serve a specific point in time rather than the sandbox's current state, take a [snapshot](/Sandboxes/Fork) first, then pass its id when forking (`snapshotId` in TypeScript, `snapshot_id` in Python):
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
// 1. Snapshot the sandbox while it is in the state you want to serve.
const snapshot = await sandbox.snapshots.create("release-candidate");
// 2. Later, fork that snapshot into an application.
const result = await sandbox.fork("my-app", {
targetType: "application",
snapshotId: snapshot.id,
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
# 1. Snapshot the sandbox while it is in the state you want to serve.
snapshot = await sandbox.snapshots.create("release-candidate")
# 2. Later, fork that snapshot into an application.
result = await sandbox.fork(
"my-app",
target_type="application",
snapshot_id=snapshot.id,
)
```
```bash HTTP API theme={null}
# 1. Create the snapshot.
curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/snapshots \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d '{ "name": "release-candidate" }'
# 2. Fork it into an application, using the id returned above.
curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/fork \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d '{
"targetType": "application",
"targetName": "my-app",
"snapshotId": "snap_abc123"
}'
```
List your existing snapshots with `sandbox.snapshots.list()`. See [Snapshots and forking](/Sandboxes/Fork) for the full snapshot lifecycle.
### Attach a custom domain at fork time
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
const result = await sandbox.fork("my-app", {
targetType: "application",
port: 3000, // port the app serves on (defaults to the sandbox's first port, else 8080)
customDomain: "app.example.com",
prefix: "myapp", // subdomain for wildcard custom domains
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
result = await sandbox.fork(
"my-app",
target_type="application",
port=3000,
custom_domain="app.example.com",
prefix="myapp",
)
```
```bash CLI theme={null}
bl fork sbx/my-sandbox app/my-app
```
```bash HTTP API theme={null}
curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/fork \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d '{
"targetType": "application",
"targetName": "my-app"
}'
```
If an application with `targetName` already exists, forking updates its compute so the backend generates a fresh revision serving the forked sandbox's state. Otherwise a new application is created. Forking into an application requires the Application Runtime feature to be enabled on your workspace.
For more on sandbox snapshots and forking sandboxes into new sandboxes, see [Sandbox snapshots and forking](/Sandboxes/Fork).
## Retrieve an application
```typescript TypeScript theme={null}
import { ApplicationInstance } from "@blaxel/core";
const app = await ApplicationInstance.get("my-app");
console.log(app.status);
```
```python Python theme={null}
from blaxel.core import ApplicationInstance
app = await ApplicationInstance.get("my-app")
print(app.status)
```
```go Go theme={null}
app, err := client.Applications.Get(ctx, "my-app")
```
```bash CLI theme={null}
bl get applications
```
```bash HTTP API theme={null}
curl https://api.blaxel.ai/v0/applications/my-app \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace"
```
## List applications
```typescript TypeScript theme={null}
import { ApplicationInstance } from "@blaxel/core";
const apps = await ApplicationInstance.list();
```
```python Python theme={null}
from blaxel.core import ApplicationInstance
apps = await ApplicationInstance.list()
```
```go Go theme={null}
apps, err := client.Applications.List(ctx, blaxel.ApplicationListParams{})
```
```bash CLI theme={null}
bl get applications
```
```bash HTTP API theme={null}
curl https://api.blaxel.ai/v0/applications \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace"
```
## Update an application
Updating an application with a new image or configuration creates a new revision.
```typescript TypeScript theme={null}
import { ApplicationInstance } from "@blaxel/core";
const app = await ApplicationInstance.get("my-app");
const updated = await app.update({
image: "app/my-image:v2",
memory: 4096,
});
```
```python Python theme={null}
from blaxel.core import ApplicationInstance
from blaxel.core.client.models import Application, ApplicationSpec, Metadata
app = await ApplicationInstance.get("my-app")
updated = await app.update(Application(
metadata=Metadata(name="my-app"),
spec=ApplicationSpec(
enabled=True,
region="us-pdx-1",
image="app/my-image:v2",
memory=4096,
),
))
```
```go Go theme={null}
updated, err := client.Applications.Update(ctx, "my-app", blaxel.ApplicationUpdateParams{
Application: blaxel.ApplicationParam{
Metadata: blaxel.MetadataParam{Name: "my-app"},
Spec: blaxel.ApplicationSpecParam{
Enabled: blaxel.Bool(true),
Region: blaxel.String("us-pdx-1"),
Image: blaxel.String("app/my-image:v2"),
Memory: blaxel.Int(4096),
},
},
})
```
```bash HTTP API theme={null}
curl -X PUT https://api.blaxel.ai/v0/applications/my-app \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d '{
"metadata": { "name": "my-app" },
"spec": {
"enabled": true,
"region": "us-pdx-1",
"image": "app/my-image:v2",
"memory": 4096
}
}'
```
## Delete an application
```typescript TypeScript theme={null}
import { ApplicationInstance } from "@blaxel/core";
await ApplicationInstance.delete("my-app");
```
```python Python theme={null}
from blaxel.core import ApplicationInstance
await ApplicationInstance.delete("my-app")
```
```go Go theme={null}
_, err := client.Applications.Delete(ctx, "my-app")
```
```bash CLI theme={null}
bl delete application my-app
```
```bash HTTP API theme={null}
curl -X DELETE https://api.blaxel.ai/v0/applications/my-app \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace"
```
## Revisions
Each deploy creates a new revision. A maximum of 5 revisions are kept per application. Revisions contain the image, environment variables, memory allocation, and port configuration.
### List revisions
```typescript TypeScript theme={null}
import { ApplicationInstance } from "@blaxel/core";
const app = await ApplicationInstance.get("my-app");
const revisions = await app.listRevisions();
```
```python Python theme={null}
from blaxel.core.client import client
from blaxel.core.client.api.applications.list_application_revisions import asyncio as list_application_revisions
revisions = await list_application_revisions(application_name="my-app", client=client)
```
```go Go theme={null}
revisions, err := client.Applications.ListRevisions(ctx, "my-app")
```
```bash HTTP API theme={null}
curl https://api.blaxel.ai/v0/applications/my-app/revisions \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace"
```
### Switch between revisions
The latest revision serves traffic by default. To roll back (or forward) to another one, set `active` to its id in the application's `revision` configuration:
```json theme={null}
{
"spec": {
"revision": {
"active": "rev_abc123"
}
}
}
```
## Custom URLs
By default, applications are accessible at a generated URL. You can configure custom URLs using verified custom domains in your workspace.
### Create or update an application with custom URLs
Pass the `urls` field in the application spec to attach a verified custom domain.
```typescript TypeScript theme={null}
import { ApplicationInstance } from "@blaxel/core";
const app = await ApplicationInstance.create({
metadata: {
name: "my-app",
},
spec: {
enabled: true,
region: "us-pdx-1",
image: "app/my-image:latest",
memory: 2048,
urls: [{ domain: "app.example.com" }],
},
});
```
```python Python theme={null}
from blaxel.core import ApplicationInstance
from blaxel.core.client.models import Application, ApplicationSpec, Metadata, AppUrl
app = await ApplicationInstance.create(Application(
metadata=Metadata(name="my-app"),
spec=ApplicationSpec(
enabled=True,
region="us-pdx-1",
image="app/my-image:latest",
memory=2048,
urls=[AppUrl(domain="app.example.com")],
),
))
```
```go Go theme={null}
app, err := client.Applications.New(ctx, blaxel.ApplicationNewParams{
Application: blaxel.ApplicationParam{
Metadata: blaxel.MetadataParam{Name: "my-app"},
Spec: blaxel.ApplicationSpecParam{
Enabled: blaxel.Bool(true),
Region: blaxel.String("us-pdx-1"),
Image: blaxel.String("app/my-image:latest"),
Memory: blaxel.Int(2048),
URLs: []blaxel.AppURLParam{{Domain: "app.example.com"}},
},
},
})
```
To add a custom URL to an existing application, use update:
```typescript TypeScript theme={null}
import { ApplicationInstance } from "@blaxel/core";
const app = await ApplicationInstance.get("my-app");
const updated = await ApplicationInstance.update("my-app", {
metadata: app.metadata,
spec: {
...app.spec,
urls: [{ domain: "app.example.com" }],
},
});
```
```python Python theme={null}
from blaxel.core import ApplicationInstance
from blaxel.core.client.models import Application, ApplicationSpec, Metadata, AppUrl
app = await ApplicationInstance.get("my-app")
updated = await app.update(Application(
metadata=app.metadata,
spec=ApplicationSpec(
enabled=True,
region="us-pdx-1",
image="app/my-image:latest",
memory=2048,
urls=[AppUrl(domain="app.example.com")],
),
))
```
```go Go theme={null}
updated, err := client.Applications.Update(ctx, "my-app", blaxel.ApplicationUpdateParams{
Application: blaxel.ApplicationParam{
Metadata: blaxel.MetadataParam{Name: "my-app"},
Spec: blaxel.ApplicationSpecParam{
Enabled: blaxel.Bool(true),
Region: blaxel.String("us-pdx-1"),
Image: blaxel.String("app/my-image:latest"),
Memory: blaxel.Int(2048),
URLs: []blaxel.AppURLParam{{Domain: "app.example.com"}},
},
},
})
```
```bash HTTP API theme={null}
curl -X PUT https://api.blaxel.ai/v0/applications/my-app \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d '{
"metadata": { "name": "my-app" },
"spec": {
"enabled": true,
"region": "us-pdx-1",
"image": "app/my-image:latest",
"memory": 2048,
"urls": [
{ "domain": "app.example.com" }
]
}
}'
```
For wildcard custom domains (e.g. `*.sandbox.example.com`), use the `subdomain` field:
```json theme={null}
{
"spec": {
"urls": [
{ "subdomain": "myapp", "domain": "sandbox.example.com" }
]
}
}
```
### Create a custom domain for an application
You can also create a custom domain scoped to a specific application using the dedicated endpoint. After creation, configure DNS records and verify domain ownership before it becomes active.
```bash theme={null}
curl -X POST https://api.blaxel.ai/v0/applications/my-app/customdomains \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d '{
"metadata": { "name": "app.example.com" },
"spec": {
"type": "applications",
"domain": "app.example.com",
"region": "us-pdx-1"
}
}'
```
The domain must be a verified custom domain in your workspace. Custom domain verification is managed in the [Infrastructure section](/Infrastructure/Custom-domains) of the Blaxel Console.
## Environment variables and secrets
Pass environment variables to your application through the spec-level `envs` field. Each entry has a `name` and `value`; set `secret: true` to mark the value as a secret so it is stored and displayed securely.
```json theme={null}
{
"spec": {
"image": "app/my-image:latest",
"memory": 2048,
"envs": [
{ "name": "DATABASE_URL", "value": "postgres://..." },
{ "name": "API_KEY", "value": "super-secret-value", "secret": true }
]
}
}
```
## Memory and compute
Memory allocation is set per revision in megabytes. The default is 2048 MB. CPU resources are allocated proportionally based on memory (CPU = memory / 2048 cores).
| Memory | CPU cores |
| - | - |
| 2048 MB | 1 core |
| 4096 MB | 2 cores |
| 8192 MB | 4 cores |
## Application deployment statuses
During the deployment process, the possible application statuses are:
* `DEPLOYING`: The application deployment is in progress.
* `DEPLOYED`: The application is running and serving traffic.
* `FAILED`: An error occurred during the build or deployment.
Create snapshots and fork sandboxes into applications or new sandboxes.
Manage environment variables and secrets for your deployments.
# Community
Source: https://docs.blaxel.ai/Community
Join the Blaxel community to ask questions, get support, and connect with other developers building AI agents and sandboxes on Blaxel.
Blaxel has an active community of developers building AI agents, sandboxes, and MCP servers. Join us to ask questions, share what you're building, and get help directly from the Blaxel team.
The [Blaxel Discord](https://discord.gg/CsWKUZUHFQ) is the best place to:
* Get real-time help from the Blaxel team and other developers
* Discuss best practices for building and deploying AI agents
* Stay up to date with new features and releases
You can also connect with Blaxel using social channels or email.
Connect with the Blaxel team and community in real time.
Follow Blaxel for product updates and company news.
Follow @blaxelAI for announcements and updates.
Browse and contribute to Blaxel's open-source repositories.
Reach the Blaxel team directly at [support@blaxel.ai](mailto:support@blaxel.ai).
Submit feedback or enquiries via the Blaxel website.
# All full repo examples
Source: https://docs.blaxel.ai/Examples/Overview
Browse sample agents, MCP servers, and full-repo applications you can use as reference or deploy directly to Blaxel from GitHub templates.
Discover [our templates on Blaxel's GitHub repository](https://github.com/blaxel-templates):
A file-centric AI cowork template with Blaxel sandboxes, shared drive, and session workspace.
An AI agent that uses secure sandboxes for automated pull request analysis and code review.
A code generation agent that builds applications from natural language and previews them live with hot-reload.
An intelligent customer support agent that handles customer inquiries across various communication channels.
A customer success agent that autonomously handles technical questions via email (inbound and outbound).
A powerful agent for automated social media post generation.
# Quickstart: Adapt an existing MCP server for Blaxel
Source: https://docs.blaxel.ai/Functions/Adapt-Existing-MCP-server
Step-by-step guide to convert an existing stdio-based MCP server to streamable HTTP transport so you can deploy and host it on Blaxel.
MCP ([Model Context Protocol](https://github.com/modelcontextprotocol)) servers provide tools (individual capabilities for accessing specific APIs or databases) that can be used by AI agents. These servers can be hosted on Blaxel's computing platform and used by AI agents via each server's global endpoint.
This quickstart walks you through the process of adapting an existing stdio-based MCP server for deployment on Blaxel using the streamable HTTP transport.
## Install the Blaxel CLI
Run the installer in a terminal:
```shell macOS and Linux theme={null}
curl -fsSL https://blaxel.ai/install.sh | sh
```
```powershell Windows theme={null}
irm https://blaxel.ai/install.ps1 | iex
```
The installer checks the Blaxel CLI (`bl`) against the release checksums, installs it, and adds it to your `PATH`. On macOS and Linux, it also installs completions for your current shell if you use zsh, bash, or fish. On Windows, [configure PowerShell completions](/cli-reference/introduction#shell-autocompletion) separately. It then runs [`bl setup`](/cli-reference/commands/bl_setup), which shows what it found and installs it when you press Enter: the [Blaxel skills](/skills-mcp#blaxel-skills) and MCP servers for the coding agents on your machine (Claude Code, Codex, Cursor and others), then the browser login. Run it again at any time to add what is missing.
```shell theme={null}
brew install blaxel-ai/blaxel/blaxel
bl setup
```
Homebrew 6.0.0+ requires explicit trust for non-official taps, so use the fully qualified formula name.
On macOS and Linux, set these variables for `sh`:
| Variable | Effect |
| - | - |
| `VERSION=v0.1.119` | Install that release instead of the latest |
| `BINDIR=/usr/local/bin` | Install to that folder instead of `~/.local/bin` |
| `BL_INSTALL_SETUP=false` | Install the CLI only, and run `bl setup` later |
| `BL_INSTALL_SKILLS=false` | Leave your coding agents alone |
| `BL_INSTALL_PATH=false` | Do not add `bl` to your `PATH` |
| `BL_INSTALL_COMPLETION=false` | Skip the shell completions |
For example, to install a specific release for all users:
```shell theme={null}
curl -fsSL https://blaxel.ai/install.sh | sudo VERSION=v0.1.119 BINDIR=/usr/local/bin sh
```
Under `sudo`, the installer still sets up your own shell, coding agents and login. In CI, it installs the CLI only and leaves the shell configuration and coding agents alone. On Windows, run `$env:BL_INSTALL_SETUP = "false"` before the installer to install the CLI only.
The installer logs you in. To log in again later, open a terminal and run:
```bash theme={null}
bl login
```
## Get the example project
This quickstart adapts the simple stdio-based MCP weather server from the [official Model Context Protocol GitHub repository](https://github.com/modelcontextprotocol/).
Clone the repository and change to the project working directory:
```bash theme={null}
git clone https://github.com/modelcontextprotocol/quickstart-resources.git
cd quickstart-resources/weather-server-typescript
```
Clone the repository and change to the project working directory:
```bash theme={null}
git clone https://github.com/modelcontextprotocol/quickstart-resources.git
cd quickstart-resources/weather-server-python
```
## Adapt the server
If your MCP server is already configured to use streamable HTTP, you may skip this step.
Blaxel uses streamable HTTP as the transport layer for MCP servers deployed on its infrastructure. If your MCP server uses stdio (as in this example), you must adapt it to use streamable HTTP instead. The host name and port for the server to bind to are automatically injected by Blaxel during deployment, as `HOST` and `PORT` environment variables.
The changes shown below are illustrative only and based on the [Blaxel MCP server template for TypeScript](https://github.com/blaxel-templates/template-mcp-ts).
Add Express to handle HTTP requests and responses:
```bash theme={null}
npm install express
npm install --save-dev @types/express
```
Here is an example of how you could adapt the existing `src/server.ts` file for a streamable HTTP server.
```tsx theme={null}
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { z } from "zod";
import express from 'express';
// ... code ...
const app = express();
app.use(express.json());
app.post('/mcp', async (req, res) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on('close', () => {
transport.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
const port = parseInt(process.env.PORT || '8000');
const host = process.env.HOST || '::';
app.listen(port, host, () => {
console.log(`MCP Server running on http://${host}:${port}/mcp`);
}).on('error', error => {
console.error('Server error:', error);
process.exit(1);
});
```
The changes shown below are illustrative only and based on the [Blaxel MCP server template for Python](https://github.com/blaxel-templates/template-mcp-py).
Here is an example of how you could adapt the existing `weather.py` file for a streamable HTTP server.
```python theme={null}
from typing import Any
import httpx, os
from mcp.server.fastmcp import FastMCP
# Initialize FastMCP server
mcp = FastMCP(
"weather",
stateless_http=True,
host=os.getenv('HOST', "::"),
port=os.getenv('PORT', "8000")
)
# ... code ...
def main():
# Initialize and run the server
mcp.run(transport='streamable-http')
if __name__ == "__main__":
main()
```
## Create the deployment configuration
Blaxel looks for a `blaxel.toml` file to configure the deployment of the MCP server on Blaxel. This file is not mandatory; if the file is not found or a required option is not set, you will be prompted for the information during deployment.
Create a new `blaxel.toml` file with the following content:
```toml theme={null}
type = "function"
[runtime]
transport = "http-stream"
```
Blaxel also automatically detects a Dockerfile at the root of the project and uses it to create and deploy a container image of your MCP server. This is a very useful feature that allows you to completely customize the deployment environment for your MCP server, including installing additional system dependencies and using specific versions of libraries or tools.
Create a new `Dockerfile` with the following content:
```dockerfile theme={null}
FROM node:20-alpine
WORKDIR /app
RUN apk update \
&& apk add build-base curl ca-certificates
COPY . .
RUN npm install
RUN npm run build
EXPOSE 8000
CMD [ "node", "build/index.js" ]
```
```dockerfile theme={null}
FROM python:3.12-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN curl -LsSf https://astral.sh/uv/install.sh | sh \
&& mv /root/.local/bin/uv /usr/local/bin/uv
WORKDIR /app
COPY . .
RUN uv sync --frozen --no-dev
EXPOSE 8000
CMD ["uv", "run", "weather.py"]
```
## Deploy the MCP server on Blaxel
Deploy the server on Blaxel by running the following command:
```bash theme={null}
bl deploy
```
Blaxel will handle the build and deployment, producing an HTTPS endpoint on Global Agentics Network. The server endpoint looks like this:
```text theme={null}
https://run.blaxel.ai/{YOUR-WORKSPACE}/functions/{YOUR-SERVER-NAME}/mcp
```
You can now [connect to this endpoint from any MCP-aware client](/Functions/Invoke-functions).
# Code mode
Source: https://docs.blaxel.ai/Functions/Code-mode
Turn any OpenAPI specification into a code mode MCP server on Blaxel, letting agents call tools by executing TypeScript code in a sandbox.
Blaxel supports "code mode" natively, enabling a more efficient way to execute tool calls over MCP.
For more information on "code mode" or "code execution with MCP", refer to blog posts by [Cloudflare](https://blog.cloudflare.com/code-mode/) and [Anthropic](https://www.anthropic.com/engineering/code-execution-with-mcp).
The typical flow is:
1. The agent receives a user prompt (e.g. "List all pets")
2. It calls `search` to discover relevant endpoints in the OpenAPI spec
3. It calls `execute` with a `fetch()` script targeting the right endpoint
4. Blaxel spins up a sandbox, injects credentials, and runs the code
5. The result flows back to the agent
Under the hood:
* The agent typically does a search first to find the right endpoint, then executes a `fetch` call.
* When an agent calls `search`, code mode evaluates a JavaScript function against the in-memory OpenAPI specification. All `$ref` pointers are auto-resolved so the agent sees the complete schema.
* When an agent calls `execute`, the code runs inside an auto-scaling sandbox with a 10-minute idle TTL.
## Deployment
Use the following code or Blaxel CLI command to deploy an MCP server in code mode. Replace the `OPENAPI_REFERENCE` variable with the URL to the API specification you want to expose.
```typescript TypeScript theme={null}
import { createFunction } from "@blaxel/core";
async function main() {
await createFunction({
body: {
metadata: {
name: "my-api-code-mode",
displayName: "API Code Mode",
},
spec: {
runtime: {
image: "blaxel/code-mode:latest",
memory: 2048,
envs: [
{ name: "OPENAPI_REFERENCE", value: "https://petstore3.swagger.io/api/v3/openapi.json" },
],
},
},
},
});
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
```
```python Python theme={null}
import asyncio
import sys
from blaxel.core.client import client
from blaxel.core.client.api.functions.create_function import asyncio as create_function
from blaxel.core.client.models.env import Env
from blaxel.core.client.models.function import Function
from blaxel.core.client.models.function_runtime import FunctionRuntime
from blaxel.core.client.models.function_spec import FunctionSpec
from blaxel.core.client.models.metadata import Metadata
async def main():
await create_function(
client=client,
body=Function(
metadata=Metadata(
name="my-api-code-mode",
display_name="API Code Mode",
),
spec=FunctionSpec(
runtime=FunctionRuntime(
image="blaxel/code-mode:latest",
memory=2048,
envs=[
Env(
name="OPENAPI_REFERENCE",
value="https://petstore3.swagger.io/api/v3/openapi.json",
),
],
),
),
),
)
asyncio.run(main())
```
```bash Blaxel CLI theme={null}
bl apply -f - <
Check status:
```bash theme={null}
bl get mcp my-api-code-mode
```
Retrieve MCP endpoint url:
```bash theme={null}
bl get mcp my-api-code-mode -o json | jq -r '.[] | .metadata.url'
```
Once status shows `DEPLOYED`, any agent can use the MCP server as a tool provider.
## Authentication
Code mode MCP servers auto-detect security schemes defined in the OpenAPI spec and map them to environment variables named `AUTH_`. For example, if the spec defines a scheme called `ApiKeyAuth`, define an environment variable in your deployment schema called `AUTH_APIKEYAUTH` with the corresponding secret:
```yaml theme={null}
envs:
- name: OPENAPI_REFERENCE
value: https://api.example.com/openapi.json
- name: AUTH_APIKEYAUTH
value: ${secrets.AUTH_APIKEYAUTH}
```
### Supported auth types
| OpenAPI scheme type | Env var usage |
| - | - |
| `http` / `bearer` | `Authorization: Bearer ` |
| `http` / `basic` | `Authorization: Basic ` |
| `apiKey` (header) | Custom header with the key value |
| `apiKey` (query) | Appended as query parameter |
| `oauth2` / `openIdConnect` | `Authorization: Bearer ` |
Build an agent that connects to a Blaxel MCP server running in code mode using Claude Agent SDK.
Deploy a self-hosted Claude Managed Agents environment on Blaxel.
# Quickstart: Develop a custom MCP server
Source: https://docs.blaxel.ai/Functions/Create-MCP-server
Develop custom MCP servers in TypeScript or Python using Blaxel SDK, with local testing via MCP Inspector and one-command deployment.
MCP servers ([Model Context Protocol](https://github.com/modelcontextprotocol)) provide a toolkit of multiple tools—individual capabilities for accessing specific APIs or databases. These servers can be interacted with using HTTP on the server’s global endpoint.
You can develop custom [MCP servers](https://modelcontextprotocol.io/introduction) in TypeScript or Python and deploy them on Blaxel by integrating a few lines of the Blaxel SDK and leveraging our other developer tools ([Blaxel CLI](../cli-reference/introduction), GitHub action, etc.).
## Quickstart
It is required to have *npm* (TypeScript) or *uv* (Python) installed to use the following command.
You can quickly **initialize a new MCP server from scratch** by using CLI command `bl new`. This initializes a new pre-scaffolded local repo where your entire code can be added.
```bash theme={null}
bl new mcp
```
You can test it by running the following command which launches **both** the MCP server and a web application to query it ([MCP Inspector](https://github.com/modelcontextprotocol/inspector), managed by MCP) locally.
```shell TypeScript theme={null}
pnpm inspect
```
```shell Python theme={null}
BL_DEBUG=true uv run mcp dev src/server.py
```
The web application is accessible at: [http://127.0.0.1:6274](http://127.0.0.1:6274/). Alternatively, you can just simply [serve the server](/Functions/Deploy-a-function) locally by running `bl serve --hotreload`.
## Develop the MCP server logic
If you open the `src/server.ts` file, you'll see the complete server implementation. It follows the MCP server standard, using streamable HTTP as the transport mechanism.
The main component you'll need to modify is the tool definition:
```typescript server.ts {13-27} theme={null}
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import "@blaxel/telemetry";
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';
import { z } from 'zod';
// Create an MCP server
const server = new McpServer({
name: 'GreetingServer',
version: '1.0.0',
});
server.registerTool(
'greet',
{
description: 'Greet someone by name',
inputSchema: { name: z.string().optional().default('World')},
outputSchema: { result: z.string() }
},
async ({ name }) => {
const output = { result: `Hello, ${name}!` };
return {
content: [{ type: 'text', text: JSON.stringify(output) }],
structuredContent: output
};
}
);
// Set up Express and HTTP transport
const app = express();
app.use(express.json());
app.post('/mcp', async (req, res) => {
// Create a new transport for each request to prevent request ID collisions
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true
});
res.on('close', () => {
transport.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
const port = parseInt(process.env.PORT || '80');
const host = process.env.HOST || '::';
app.listen(port, host, () => {
console.log(`MCP Server running on {host}:${port}/mcp`);
}).on('error', error => {
console.error('Server error:', error);
process.exit(1);
});
```
Remember that the `name`, `description`, and *parameters* are crucial—they help your agent understand how your tool functions.
If you open the `src/server.py` file, you'll see the complete server implementation. It follows the MCP server standard, using streamable HTTP as the transport mechanism.
The main component you'll need to modify is the tool definition:
```python server.py {12-16} theme={null}
import os
from mcp.server.fastmcp import FastMCP
mcp = FastMCP(
"GreetingServer",
stateless_http=True,
host=os.getenv('HOST', "::"),
port=os.getenv('PORT', "80")
)
# Add a simple tool to demonstrate the server
@mcp.tool()
def greet(name: str = "World") -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
# Run server with streamable_http transport
if __name__ == "__main__":
mcp.run(transport="streamable-http")
```
Read our complete guide for deploying your custom MCP server on Blaxel.
## Template directory reference
### blaxel.toml
The MCP server deployment can be configured via the ***blaxel.toml*** file in your MCP server directory. This file is not mandatory; if the file is not found or a required option is not set, you will be prompted for the information during deployment.
```toml theme={null}
name = "my-mcp-server"
workspace = "my-workspace"
type = "function"
[env]
DEFAULT_CITY = "San Francisco"
[[triggers]]
id = "trigger-my-mcp"
type = "http"
[triggers.configuration]
path = "functions/my-mcp" # This will create this endpoint on the following base URL: https://run.blaxel.ai/{YOUR-WORKSPACE}/
authenticationType = "public"
```
* `name`, `workspace`, and `type` fields are optional and serve as default values. Any bl command run in the folder will use these defaults rather than prompting you for input.
* `[env]` section defines environment variables that the MCP server can access via the SDK. Note that these are NOT [secrets](../Agents/Variables-and-secrets).
* `[[triggers]]` and `[triggers.configuration]` sections define ways to send requests to the MCP servers. You can also make them either private (default) or public (`authenticationType = "public"`).
Additionally, when developing in Python, you can define an `[entrypoint]` section to specify how Blaxel is going to start your server.
```toml theme={null}
...
[entrypoint]
prod = ".venv/bin/python3 src/server.py"
dev = "npx nodemon --exec uv run python src/server.py"
...
```
* `prod`: this is the command that will be used to serve your MCP server
```bash theme={null}
.venv/bin/python3 src/server.py
```
* `dev`: same as prod in dev mode, it will be used with the command `--hotreload`. Example:
```bash theme={null}
npx nodemon --exec uv run python src/server.py
```
This `entrypoint` section is optional. If not specified, Blaxel will automatically detect the MCP server’s content and configure your server’s startup settings.
In TypeScript, entrypoints are managed in the `scripts` in the `package.json` file at the root of the directory.
* `scripts.start` : start the server locally through the TypeScript command, to avoid having to build the project when developing.
* `scripts.build` : build the project. It is done automatically when deploying.
* `scripts.prod` : start the server remotely on Blaxel from the dist folder, the project needs to have been built before.
* `scripts.dev` : same as start, but with hotreload. It's useful when developing locally, each file change is reflected immediately.
The remaining fields in *package.json* follow standard JavaScript/TypeScript project conventions. Feel free to add any dependencies you need, but keep in mind that devDependencies are only used during the build process and are removed afterwards.
## List MCP servers
To retrieve all MCP servers in your workspace, use the Management API list endpoint with pagination.
Pagination is recommended for list operations because retrieving all available resources in a single request is slow and resource-intensive, and therefore does not scale well.
Request the first page:
```bash theme={null}
curl 'https://api.blaxel.ai/v0/functions?limit=50' \
-H 'X-Blaxel-Authorization: Bearer YOUR-API-KEY' \
-H 'Blaxel-Version: 2026-04-28'
```
The response includes a `data` array and a `meta` object:
```json theme={null}
{
"data": [ ... ],
"meta": {
"hasMore": true,
"nextCursor": "...",
"total": 36
}
}
```
Pass `meta.nextCursor` as `cursor` on the next request and repeat until `meta.hasMore` is `false`:
```bash theme={null}
curl 'https://api.blaxel.ai/v0/functions?limit=50&cursor=NEXT_CURSOR' \
-H 'X-Blaxel-Authorization: Bearer YOUR-API-KEY' \
-H 'Blaxel-Version: 2026-04-28'
```
For accounts with multiple workspaces, specify the workspace as an additional request parameter, such as `?workspace=WORKSPACE_NAME&...`, or via an additional `X-Blaxel-Workspace: WORKSPACE_NAME` header in the request.
For more information, refer to the [API reference documentation](/api-reference/introduction#pagination).
Read our complete guide for deploying your custom MCP server on Blaxel.
# Deploy custom MCP servers
Source: https://docs.blaxel.ai/Functions/Deploy-a-function
Host custom MCP servers on Blaxel as serverless, autoscalable endpoints with the CLI, GitHub integration, or Dockerfile-based deployments.
Blaxel provides a serverless infrastructure to instantly deploy MCP servers. You receive a global inference endpoint for each deployment, and your workloads are served optimally to dramatically accelerate cold-start and latency. The main way to deploy an MCP server on Blaxel is by **using Blaxel CLI.**
## Deploy an MCP server with Blaxel CLI
This section assumes you have developed the MCP server locally, as explained [in this documentation](/Functions/Create-MCP-server), and are ready to deploy it.
### Serve locally
Blaxel offers you a way to serve locally either:
You can test it by running the following command which launches **both** the MCP server and a web application to query it ([MCP Inspector](https://github.com/modelcontextprotocol/inspector), managed by MCP) locally.
```shell TypeScript theme={null}
pnpm inspect
```
```shell Python theme={null}
BL_DEBUG=true uv run mcp dev src/server.py
```
The web application is accessible at: [http://127.0.0.1:6274](http://127.0.0.1:6274/). Alternatively, you can just simply [serve the server](/Functions/Deploy-a-function) locally by running `bl serve --hotreload`.
You can serve the MCP server locally in order to make the entrypoint function (by default: `server.ts` / `server.py`) available on a local endpoint.
Run the following command to serve the MCP server:
```bash theme={null}
bl serve
```
You can then create an MCP Client to communicate with your server. When testing locally, communication happens over stdio, but when deployed on Blaxel, your server will use streamable HTTP instead.
Add the flag `--hotreload` to get live changes.
```bash theme={null}
bl serve --hotreload
```
### Deploy on production
You can deploy the MCP server in order to make the entrypoint function (by default: `server.ts` / `server.py`) **available on a global hosted endpoint**. When deploying to Blaxel, you get a dedicated endpoint that enforces your [deployment policies](../Model-Governance/Policies).
Run the following command to build and deploy the MCP server on Blaxel:
```bash theme={null}
bl deploy
```
You can alternatively use `bl push` to build and push the MCP container image to the Blaxel registry. `bl push` only publishes the image; it does not create a new sandbox deployment, update existing deployments, or restart running processes. This is useful for preparing images in advance.
You can now [connect to the MCP server](/Functions/Invoke-functions) either from an agent on Blaxel (using the Blaxel SDK), or from an external client that supports HTTP.
```typescript In TypeScript theme={null}
// Import tool adapter (in whichever framework format):
import { blTools } from "@blaxel/langchain";
// or
import { blTools } from "@blaxel/llamaindex";
// or
import { blTools } from "@blaxel/mastra";
// or
import { blTools } from "@blaxel/vercel";
// or
// …
const tools = blTools(['blaxel-search'])
```
```python In Python theme={null}
# Import tool adapter (in whichever framework format):
from blaxel.pydantic import bl_tools
# or
from blaxel.langgraph import bl_tools
# or
from blaxel.crewai import bl_tools
# or
from blaxel.googleadk import bl_tools
# or
from blaxel.openai import bl_tools
#or
# …
tools = await bl_tools(['blaxel-search'])
```
Learn how to run tool calls through your MCP server.
### Customize an MCP server deployment
You can set custom parameters for an MCP server deployment (e.g. specify the server name, etc.) in the `blaxel.toml` file at the root of your directory.
For more information on MCP deployment settings, refer to the reference section down at the bottom of this guide.
### Deploy with a Dockerfile
While Blaxel uses predefined, optimized container images to build and deploy your code, you can also deploy your workload using your own [Dockerfile](https://docs.docker.com/reference/dockerfile/).
Deploy resources using a custom Dockerfile.
### Deploy multiple resources at once
Using a custom Dockerfile allows for [deploying multiple agents & MCPs from the same repository](../Agents/Deploy-multiple) with shared dependencies.
Deploy multiple agents & MCP servers with shared context from a single repository.
## Reference for deployment life-cycle
### Maximum runtime
* Deployed MCP servers have a maximum runtime of 15 minutes.
### Manage revisions
As you iterate on your software development, you will need to update the version of a function that is currently deployed and used by your consumers. Every time you build a new version of your function, this creates a **revision**. Blaxel stores the last 5 revisions for each object.
Revisions are atomic builds of your deployment that can be either deployed (accessible via the inference endpoint) or not. This system enables you to:
* **rollback a deployment** to its exact state from an earlier date
* create a revision without immediate deployment to **prepare for a future release**
* implement progressive rollout strategies, such as **canary deployments**
Important: Revisions are not the same as versions. You cannot use revisions to return to a previous configuration and branch off from it. For version control, use your preferred system (such as GitHub) alongside Blaxel.
Deployment revisions are updated following a **blue-green** paradigm. The Global Inference Network will wait for the new revision to be completely up and ready before routing requests to the new deployment. You can also set up a **canary deployment** to split traffic between two revisions (maximum of two).
When making a deployment using Blaxel CLI (`bl deploy`), the new traffic routing depends on the `--traffic` option. Without this option specified, Blaxel will automatically deploy the new revision with full traffic (100%) if the previous deployment was the latest revision. Otherwise, it will create the revision without deploying it (0% traffic).
### Code mode
Blaxel supports ["code mode"](/Functions/Code-mode) natively, enabling a more efficient way to execute tool calls over MCP.
## Deployment reference
The MCP server deployment can be configured via the ***blaxel.toml*** file in your MCP server directory. This file is not mandatory; if the file is not found or a required option is not set, you will be prompted for the information during deployment.
```toml theme={null}
name = "my-mcp-server"
workspace = "my-workspace"
type = "function"
[env]
DEFAULT_CITY = "San Francisco"
[[triggers]]
id = "trigger-my-mcp"
type = "http"
[triggers.configuration]
path = "functions/my-mcp" # This will create this endpoint on the following base URL: https://run.blaxel.ai/{YOUR-WORKSPACE}/
authenticationType = "public"
```
* `name`, `workspace`, and `type` fields are optional and serve as default values. Any bl command run in the folder will use these defaults rather than prompting you for input.
* `[env]` section defines environment variables that the MCP server can access via the SDK. Note that these are NOT [secrets](../Agents/Variables-and-secrets).
* `[[triggers]]` and `[triggers.configuration]` sections define ways to send requests to the MCP servers. You can also make them either private (default) or public (`authenticationType = "public"`).
Additionally, when developing in Python, you can define an `[entrypoint]` section to specify how Blaxel is going to start your server.
```toml theme={null}
...
[entrypoint]
prod = ".venv/bin/python3 src/server.py"
dev = "npx nodemon --exec uv run python src/server.py"
...
```
* `prod`: this is the command that will be used to serve your MCP server
```bash theme={null}
.venv/bin/python3 src/server.py
```
* `dev`: same as prod in dev mode, it will be used with the command `--hotreload`. Example:
```bash theme={null}
npx nodemon --exec uv run python src/server.py
```
This `entrypoint` section is optional. If not specified, Blaxel will automatically detect the MCP server’s content and configure your server’s startup settings.
In TypeScript, entrypoints are managed in the `scripts` in the `package.json` file at the root of the directory.
* `scripts.start` : start the server locally through the TypeScript command, to avoid having to build the project when developing.
* `scripts.build` : build the project. It is done automatically when deploying.
* `scripts.prod` : start the server remotely on Blaxel from the dist folder, the project needs to have been built before.
* `scripts.dev` : same as start, but with hotreload. It's useful when developing locally, each file change is reflected immediately.
The remaining fields in *package.json* follow standard JavaScript/TypeScript project conventions. Feel free to add any dependencies you need, but keep in mind that devDependencies are only used during the build process and are removed afterwards.
### Deployment manifests (advanced usage)
When `bl deploy` runs, it generates a YAML configuration manifest automatically and deploys it to Blaxel's hosting infrastructure. You can also create custom manifest files in the `.blaxel` folder and deploy them using the following command:
```bash theme={null}
bl apply -f ./my-deployment.yaml
```
Read our [reference for MCP server deployments](https://docs.blaxel.ai/api-reference/functions/get-function-by-name).
Learn how to run tool calls on your MCP server.
Build an agent that connects to a Blaxel MCP server running in code mode using Claude Agent SDK.
# Query MCP servers
Source: https://docs.blaxel.ai/Functions/Invoke-functions
Connect to MCP servers deployed on Blaxel via streamable HTTP endpoints. Covers authentication, public access, and integration with agents and apps.
Blaxel uses **streamable HTTP** as its MCP transport mechanism. MCP servers deployed on Blaxel are only hosted server-side and cannot be installed locally.
## MCP server endpoint
When you deploy an MCP server on Blaxel, an **HTTP endpoint** is generated on Global Agentics Network to connect to the server.
The server endpoint looks like this:
```http Connect to an MCP server theme={null}
https://run.blaxel.ai/{YOUR-WORKSPACE}/functions/{YOUR-SERVER-NAME}/mcp
```
### Endpoint authentication
By default, MCP servers deployed on Blaxel aren’t public. It is necessary to authenticate all connections, via a [bearer token](../Security/Access-tokens).
The evaluation of authentication/authorization for messages is managed by the Global Agentics Network based on the [access given in your workspace](../Security/Workspace-access-control).
You can make an MCP server **public**, meaning it will bypass Blaxel authentication and you will have to implement your own authentication inside the server. This can be done through the `blaxel.toml` configuration file.
The MCP server deployment can be configured via the ***blaxel.toml*** file in your MCP server directory. This file is not mandatory; if the file is not found or a required option is not set, you will be prompted for the information during deployment.
```toml theme={null}
name = "my-mcp-server"
workspace = "my-workspace"
type = "function"
[env]
DEFAULT_CITY = "San Francisco"
[[triggers]]
id = "trigger-my-mcp"
type = "http"
[triggers.configuration]
path = "functions/my-mcp" # This will create this endpoint on the following base URL: https://run.blaxel.ai/{YOUR-WORKSPACE}/
authenticationType = "public"
```
* `name`, `workspace`, and `type` fields are optional and serve as default values. Any bl command run in the folder will use these defaults rather than prompting you for input.
* `[env]` section defines environment variables that the MCP server can access via the SDK. Note that these are NOT [secrets](../Agents/Variables-and-secrets).
* `[[triggers]]` and `[triggers.configuration]` sections define ways to send requests to the MCP servers. You can also make them either private (default) or public (`authenticationType = "public"`).
### Runtime limit
MCP server runtime has a hard limit of 15 minutes.
## Call the MCP server
You can connect to your MCP server and send requests in several ways (code samples below):
* **use the Blaxel SDK to retrieve tools**: best when developing an agent, particularly when running on Blaxel
* **integrate with AI tools**: integrate with AI agents and other MCP-aware tools
* **connect from your code directly**: suitable for custom implementations requiring server connection to list and call tools
* **connect from the Blaxel Console's Playground**: best for testing and validating server functionality
### Use Blaxel SDK to retrieve tools
The following code example demonstrates how to use the Blaxel SDK to retrieve and pass an MCP server’s tools when building an agent.
```typescript In TypeScript theme={null}
// Import tool adapter (in whichever framework format):
import { blTools } from "@blaxel/langchain";
// or
import { blTools } from "@blaxel/llamaindex";
// or
import { blTools } from "@blaxel/mastra";
// or
import { blTools } from "@blaxel/vercel";
// or
// …
const tools = blTools(['blaxel-search'])
```
```python In Python theme={null}
# Import tool adapter (in whichever framework format):
from blaxel.pydantic import bl_tools
# or
from blaxel.langgraph import bl_tools
# or
from blaxel.crewai import bl_tools
# or
from blaxel.googleadk import bl_tools
# or
from blaxel.openai import bl_tools
#or
# …
tools = await bl_tools(['blaxel-search'])
```
### Integrate with AI tools
Add the MCP server to MCP-aware applications and tools. This section has instructions for some popular applications.
Blaxel MCP endpoints do not support OAuth. Use bearer-token authentication only. You must provide your [Blaxel API key](../Security) in the `Authorization` header. If you have access to multiple workspaces, include the workspace header as well.
* Required: `Authorization: Bearer `
* Optional (multi-workspace): `X-Blaxel-Workspace: `
Add the server to `~/.cursor/mcp.json`:
```json theme={null}
{
"mcpServers": {
"blaxel": {
"url": "https://run.blaxel.ai//functions/blaxel-search/mcp",
"headers": {
"Authorization": "Bearer ",
"X-Blaxel-Workspace": ""
}
}
}
}
```
[Add the remote HTTP server to your Claude Code](https://docs.anthropic.com/en/docs/claude-code/mcp#option-3%3A-add-a-remote-http-server) by running the following command:
```bash theme={null}
claude mcp add --transport http blaxel https://run.blaxel.ai//functions/blaxel-search/mcp \\
--header "Authorization: Bearer " \\
--header "X-Blaxel-Workspace: "
```
Add to `~/.codeium/windsurf/mcp_config.json`:
```json theme={null}
{
"mcpServers": {
"blaxel": {
"url": "https://run.blaxel.ai//functions/blaxel-search/mcp",
"headers": {
"Authorization": "Bearer ",
"X-Blaxel-Workspace": ""
}
}
}
}
```
Add to `~/.config/goose/config.yaml`:
```yaml theme={null}
extensions:
blaxel:
enabled: true
type: streamable_http
name: blaxel
description: Blaxel MCP
uri: https://run.blaxel.ai//functions/blaxel-search/mcp
envs: {}
env_keys: []
headers:
Authorization: Bearer
X-Blaxel-Workspace:
bundled: null
available_tools: []
```
### Directly connect from your code
Below are snippets of code to connect to an MCP server that is deployed on Blaxel. You will need the following information:
* `BL_API_KEY`: an [API key](../Security/Access-tokens) to connect to your Blaxel workspace
* `BL_WORKSPACE`: the slug ID for your workspace
* `MCP_SERVER_NAME`: the slug name for your MCP server
```typescript In TypeScript theme={null}
import { settings } from "@blaxel/core";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import dotenv from 'dotenv';
// Load environment variables from .env file
dotenv.config();
// Initialize client
const client = new Client({
name: 'streamable-http-client',
version: '2.0.0'
});
// Initialize transport
const baseUrl = `${settings.runUrl}/${settings.workspace}/functions/blaxel-search/mcp`
const transport = new StreamableHTTPClientTransport(new URL(baseUrl), { requestInit: { headers: settings.headers } });
// Connect client to transport
await client.connect(transport);
// List tools
const response = await client.listTools();
console.log(`Tools retrieved, number of tools: ${response.tools.length}`);
```
```python In Python theme={null}
import asyncio
from mcp.client.session import ClientSession
from mcp.client.streamable_http import streamablehttp_client
from blaxel import settings
async def run():
"""Run the completion client example."""
url = f"{settings.run_url}/{settings.workspace}/functions/blaxel-search/mcp"
async with streamablehttp_client(url, settings.headers) as (read, write, _):
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()
tools = await session.list_tools()
print(f"Tools retrieved, number of tools: {len(tools.tools)}")
asyncio.run(run())
```
Requirements are as follows:
```json package.json (In TypeScript) theme={null}
"dependencies": {"@blaxel/core": …}
```
```txt requirements.txt (In Python) theme={null}
python-dotenv
blaxel
```
### Connect to pre-built servers
Blaxel’s pre-built MCP servers offer two methods:
* `tools/list` : method that **lists the available tools** and their schemas, allowing consumers (you or agents) to discover the function’s capabilities.
* `tools/call` : method that lets consumers **execute individual tools**. It requires params with two keys:
* `name`: the name of the tool to run, obtained from the listing endpoint above
* `arguments`: an object with the key and values of input parameters for the execution, obtained from the listing endpoint above
Example of `tools/list` outbound message on a Brave Search toolkit (make sure to fill in the authentication token).
```json theme={null}
{
"method":"tools/list",
"jsonrpc":"2.0",
"id":1
}
```
This one returns two tools in the function: ***brave\_web\_search*** and ***brave\_local\_search***.
```json theme={null}
{
"result": {
"tools": [
{
"name": "blaxel_web_search",
"description": "Performs a web search using the Brave Search API, ideal for general queries, news, articles, and online content. Use this for broad information gathering, recent events, or when you need diverse web sources. Supports pagination, content filtering, and freshness controls. Maximum 20 results per request, with offset for pagination. ",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string"
},
"count": {
"type": "number"
},
"offset": {
"type": "number"
}
},
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}
},
{
"name": "blaxel_local_search",
"description": "Searches for local businesses and places using Brave's Local Search API. Best for queries related to physical locations, businesses, restaurants, services, etc. Returns detailed information including:\n- Business names and addresses\n- Ratings and review counts\n- Phone numbers and opening hours\nUse this when the query implies 'near me' or mentions specific locations. Automatically falls back to web search if no local results are found.",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string"
},
"count": {
"type": "number"
}
},
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}
}
]
},
"jsonrpc": "2.0",
"id": "1"
}
```
Example of `tools/call` outbound message on the ***brave\_web\_search*** tool.
```json theme={null}
{
"jsonrpc":"2.0",
"id":2,
"method":"tools/call",
"params":{
"name":"blaxel_web_search",
"arguments":{
"query":"What is the current weather in NYC?",
"count":1,
"offset":1
}
}
}
```
### Via Blaxel console
Requests to an MCP server can be made from the Blaxel console from the server deployment’s **Playground** page.
# MCP servers
Source: https://docs.blaxel.ai/Functions/Overview
Deploy MCP servers on Blaxel as serverless, autoscalable APIs to equip your AI agents with tools, integrations, and external system access.
MCP servers (called `functions` in Blaxel API) are lightweight programs that expose specific capabilities (accessing databases, APIs, local files, etc.) through the standardized [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP). They are designed to equip [agents](/Agents/Overview) with tools to interact with the world.
## Essentials
MCP Server Hosting is a **serverless computing service that allows you to host remote MCP servers** without having to manage infrastructure. It gives you full [observability and tracing](/Observability/Overview) out of the box.
You only provide the MCP server code, and Blaxel automates its hosting, execution and scaling - providing you with one single global endpoint to access the MCP server. The deployed server uses streamable HTTP as the transport layer.
Blaxel SDK lets you **retrieve the tools from an MCP server in your code**. When both an agent and MCP server run on Blaxel, the tool call in the MCP server is executed separately from the agent logic. This ensures not only optimal resource utilization, but also better design practice for your agentic system.
### Requirements & limitations
* Your MCP server must implement streamable HTTP as its transport layer.
* Deployed MCP servers have a maximum runtime of 15 minutes.
## MCP hosting on Blaxel
Blaxel uses **streamable HTTP** as its MCP transport mechanism. MCP servers deployed on Blaxel are only hosted server-side and cannot be installed locally.
For developers interested in the technical details, our implementation is available open-source through [Blaxel's Supergateway](https://github.com/blaxel-ai/supergateway) and [Blaxel's SDK](https://github.com/blaxel-ai/toolkit/blob/main/sdk-ts/src/functions/mcp.ts).
There are two routes you can take when hosting MCPs on Blaxel:
* Use one of the pre-built MCP servers from the Blaxel Store (e.g. [Google Maps](/Integrations/Google-Maps), [Gmail](/Integrations/Gmail))
* Deploy a custom MCP server from your code, or [adapt an existing stdio-based server](/Functions/Adapt-Existing-MCP-server) for Blaxel
Convert a stdio MCP server to streamable HTTP and deploy it.
Build a new MCP server from scratch with Blaxel SDK.
Host your MCP server on Blaxel as a serverless endpoint.
Run tool calls through your deployed MCP server.
Turn any OpenAPI spec into a code mode MCP server.
Manage environment variables and secrets for MCP servers.
## Related products
MCP servers integrate with other Blaxel products. [Agents](/Agents/Overview) use MCP servers as tools, [sandboxes](/Sandboxes/Overview) include a [built-in MCP server](/Sandboxes/MCP) for agent-driven operations, and [model APIs](/Models/Overview) provide the LLM backbone for agent reasoning. See the [SDK reference](/sdk-reference/introduction) for programmatic access. Blaxel also offers [resource-management and documentation MCP servers](/skills-mcp) for your coding assistant.
# Variables and secrets for MCP servers
Source: https://docs.blaxel.ai/Functions/Variables-and-secrets
Configure environment variables and secrets for Blaxel MCP servers to securely store API keys, credentials, and runtime configuration values.
Environment variables are retrieved first from your `.env` file (or any other file you specify when deploying, see down below), and if not found there, from the `[env]` section of `blaxel.toml`. This fallback mechanism allows for two kinds of variables:
* secrets
* simple environment variables
## Secrets
Create a file named `.env` at the root of your project to store secrets. Blaxel will retrieve them from this default location upon deployment (see down below to override). Add the `.env` file to your `.gitignore` to prevent committing sensitive variables.
```bash theme={null}
MY_SECRET=123456
```
You can then use secrets in your code as follows:
```typescript TypeScript theme={null}
import { env } from "@blaxel/core";
console.info(env.MY_SECRET); // 123456
```
```python Python theme={null}
import os
os.environ.get('MY_SECRET')
```
### Use secrets from another env file
To separate local *.env* from production, specify a different environment file when deploying your resource to Blaxel using the `--env-file` argument in Blaxel CLI:
```bash theme={null}
bl deploy --env-file anyenvfile
```
This lets you specify a different environment file for production secrets instead of relying on the default *.env*, e.g.:
* Use `.env` for local development
* Use `.env.prod` for production deployment
## Variables
You can define variables inside your agent or MCP server in the `blaxel.toml` file at root level of your project. These variables are NOT intended to be used as secrets, but as configuration variables.
```toml blaxel.toml {6} theme={null}
name = "..."
workspace = "..."
type = "function"
[env]
DEFAULT_CITY = "San Francisco"
```
You can then use it in your code as follows:
```typescript TypeScript theme={null}
import { env } from "@blaxel/core";
console.info(env.DEFAULT_CITY); // San Francisco
```
```python Python theme={null}
import os
os.environ.get('DEFAULT_CITY')
```
## Build variables
Build variables let you pass secrets and configuration values into the Docker build phase without exposing them at runtime. This is useful when your build process needs credentials that should never appear inside the deployed MCP server.
Two options exist: `.env.build` for build secrets and `blaxel.toml` file for non-secret build variables.
### .env.build
Create a `.env.build` file in the root of your project for build secrets. A common example of this is installing private npm packages, which require an `NPM_TOKEN` during `npm install`. Variables defined here are injected during the build phase only and are never persisted in the runtime environment.
```bash .env.build theme={null}
MY_SECRET_BUILD_VAR=I_AM_A_SECRET
```
Use the `--build-env-file` argument to `bl deploy` to specify a custom file name or path instead of the default `.env.build`.
Ensure that `.env.build` is ignored during commits to avoid accidentally making secrets public.
### blaxel.toml
Non-secret build variables can be placed in the `blaxel.toml` file, as in the example below:
```toml blaxel.toml theme={null}
type = "function"
name = "my-function"
[build.args]
MY_BUILD_VAR = "I_AM_NOT_A_SECRET"
```
## Reserved variables
The following variables are reserved by Blaxel:
`PORT` : Port of the HTTP server, it needs to be set to allow Blaxel platform to configure it
`HOST` : Host of the HTTP server, it needs to be set to allow Blaxel platform to configure it
Internal URL for Blaxel platform, to avoid linking multiple instances through the Internet
`BL_AGENT_${envVar}_SERVICE_NAME`
`BL_FUNCTION_${envVar}_SERVICE_NAME`
`BL_RUN_INTERNAL_HOSTNAME`: internal run url
Override URL to link multiple agents and MCP servers together locally
`BL_AGENT_${envVar}_URL`
`BL_FUNCTION_${envVar}_URL`
Metadata automatically set by Blaxel platform in production
`BL_WORKSPACE` : workspace name
`BL_NAME` : name of the function or the agent
`BL_TYPE` : function or agent
Authentication environment variables
`BL_CLIENT_CREDENTIALS` : client credentials, used by Blaxel in production with a workspace service account
`BL_API_KEY` : can be set in your code to connect with the platform (locally or from a server not on Blaxel platform)
`BL_LOG_LEVEL` : Log level, default to info, can be set to debug,warn,error
`BL_DEBUG_TELEMETRY`: Enable telemetry debug mode, will print each interaction with OpenTelemetry
`BL_ENABLE_OPENTELEMETRY`: Enable OpenTelemetry, it's set automatically by the platform in production
# OAuth flow for integrations
Source: https://docs.blaxel.ai/Functions/oauth-flow
Set up a user-facing OAuth flow so end-users can authorize integration connections to third-party services through your Blaxel application.
This page covers end-user OAuth: letting your **end users** authorize third-party services (e.g. Gmail) so their integration can be bound to an MCP server you deploy. It is **not** about authenticating a client to a Blaxel MCP endpoint. MCP servers that you deploy on Blaxel authenticate clients with bearer tokens. To sign a client in to the hosted Blaxel MCP server with OAuth, see [Blaxel Skills & MCP](/skills-mcp).
Some Blaxel integrations support [OAuth](https://oauth.net/2/)-based authentication. You can automate such a flow to allow multiple users (tenants) to go through an OAuth flow and each have their integration.
This approach is particularly useful when you need to automate the creation of dedicated MCP servers for each tenant with customized access permissions. This guide takes the example of creating such a flow for a [Gmail integration](/Integrations/Gmail).
## **Prerequisites**
* A Google OAuth application configured with the scope [`https://www.googleapis.com/auth/gmail.send`](https://www.googleapis.com/auth/gmail.send) (see below for guide)
* Client ID and Client Secret from your OAuth app
* A backend server to handle the OAuth flow
### 1. Create the APP
* Go to [https://console.cloud.google.com/auth/clients](https://console.cloud.google.com/auth/clients)

* Select *Web application*
* Choose a name. It will be displayed when your user logs in through Google
* The redirect URIs will be the URL where your user will be redirected to after Google authorizes the request. It must be server-side as it will need to access secret credentials.
Finish creating the application. You’ll be given a *client\_id* and *client\_secret:* keep them securely.
### 2. Configure scope
Scopes are used to request sufficient access on the user’s account. In this example, we want to connect their account to the MCP server to send emails.
* Go to Data access

* Click on “Add or remove scopes”
* Add `gmail.send`

Some scopes (like this one) require an HTTPS callback URL. You won’t be able to test them easily locally without using *ngrok* or a similar tool which allows you to have an HTTPS URL bound to your localhost.By default, it will work with your own account without any review by Google. You can look for the Audience tab on the left to add more users. To make it global, you will need to launch a review process from Google with the “Publish app” button.**We recommend creating a separate OAuth flow specifically for the Gmail integration.** Don't simply add the Gmail scope to an existing OAuth flow, as this would require all users to grant email permissions even when they don't need Gmail functionality.
## **Overview of the OAuth flow**
The integration follows this redirect pattern:
```text theme={null}
UI → Your Backend → Google → Your Backend → UI
```
## **Step-by-step guide**
### **Step 1: User initiates connection**
* In your app’s UI, user clicks a button to “connect with Google” (or equivalent) during integration setup
* UI redirects to your backend OAuth endpoint
### **Step 2: Backend initiates Google OAuth**
Your backend should redirect to Google's OAuth URL with these parameters:
**Required Scopes:**
* `https://www.googleapis.com/auth/gmail.send`
* `openid`
* `profile`
* `email`
**OAuth URL:**
```text theme={null}
https://accounts.google.com/o/oauth2/auth?client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope=email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.send+openid+profile&response_type=code&access_type=offline
```
### **Step 3: Google authorization**
* Google prompts user to log in and approve requested scopes
* Google redirects back to your redirect\_uri with a temporary authorization code
### **Step 4: Exchange Code for Tokens**
Your backend exchanges the authorization code for access and refresh tokens:
**Example: Token Exchange (Python)**
```python theme={null}
import requests
import json
def exchange_code_for_tokens(auth_code, client_id, client_secret, redirect_uri):
"""
Exchange authorization code for access and refresh tokens
"""
token_url = "https://oauth2.googleapis.com/token"
data = {
'code': auth_code,
'client_id': client_id,
'client_secret': client_secret,
'redirect_uri': redirect_uri,
'grant_type': 'authorization_code'
}
try:
response = requests.post(token_url, data=data)
response.raise_for_status()
token_data = response.json()
return {
'access_token': token_data['access_token'],
'refresh_token': token_data['refresh_token'],
'expires_in': token_data['expires_in']
}
except requests.exceptions.RequestException as e:
print(f"Token exchange failed: {e}")
if hasattr(e.response, 'json'):
print(f"Error details: {e.response.json()}")
raise
```
**Example: cURL request**
```bash theme={null}
curl -X POST https://oauth2.googleapis.com/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "code=AUTHORIZATION_CODE" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "redirect_uri=YOUR_REDIRECT_URI" \
-d "grant_type=authorization_code"
```
### **Step 5: Create integration**
Once you have the refresh token, create the integration on Blaxel (for an MCP server in this case) with these parameters:
```bash theme={null}
// Create the integration connection on blaxel (store the secrets values)
curl -X POST https://api.blaxel.ai/v0/integrations/connections \
-H "Content-Type: application/json" \
-H "X-Blaxel-Workspace: YOUR_WORKSPACE_ID" \
-H "Authorization: Bearer BL_API_KEY" \
-d '{
"metadata": {
"name": "gmail-google-oauth",
"displayName": "Gmail Google OAuth Connection"
},
"spec": {
"integration": "gmail",
"secret": {
"CLIENT_ID": "your-google-client-id.apps.googleusercontent.com",
"CLIENT_SECRET": "your-google-client-secret",
"REFRESH_TOKEN": "user-refresh-token-from-oauth"
}
}
}'
```
**Parameters Required:**
* `CLIENT_ID`: Your Google OAuth application client ID
* `CLIENT_SECRET`: Your Google OAuth application client secret
* `REFRESH_TOKEN`: The refresh token obtained from the OAuth flow
You can then create the [actual MCP server](../Functions/Overview) that uses this integration connection:
```bash theme={null}
// Create the MCP bound to the integration connection
curl -X POST https://api.blaxel.ai/v0/functions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer BL_API_KEY" \
-H "X-Blaxel-Workspace: YOUR_WORKSPACE_ID" \
-d '{
"metadata": {
"name": "gmail-mcp-server",
"displayName": "Gmail MCP Server"
},
"spec": {
"type": "mcp",
"integrationConnections": ["gmail-google-oauth"],
"runtime": {
"type": "mcp"
}
}
}'
```
### **Step 6: Complete Integration**
* Save MCP configuration in your system
* Redirect user back to frontend with success confirmation
* Integration is now ready for use
## **Troubleshooting**
* **Invalid scope error**: Ensure Gmail API is enabled in Google Cloud Console
* **Redirect URI mismatch**: Verify redirect URI matches exactly in Google OAuth app settings
* **Token refresh issues**: Check that `access_type=offline` is included in initial OAuth request
# Get started
Source: https://docs.blaxel.ai/Get-started
Step-by-step quickstart guide to install the Blaxel CLI, create a cloud sandbox, run processes, manage files, and deploy your first AI agent.
Blaxel is the **infrastructure for autonomous agents**. Our platform lets you keep infinite, secure sandboxes on automatic standby while co-hosting your agents and context for near instant latency.
**Using Cursor, Claude Code, Codex, Goose, or another AI coding agent?**
The [Blaxel skills](./skills-mcp) give your agent the ability to deploy AI agents, create sandboxes for code execution, host MCP servers, run batch jobs, and more...all using simple prompts, zero code required!
Installing Blaxel (step 1 below) sets them up for the coding agents on your machine, with the Blaxel MCP servers. If you already have the CLI, run:
```shell theme={null}
bl setup
```
## Quickstart
Welcome there! 👋 Make sure you have created an account on Blaxel (here → [https://app.blaxel.ai](https://app.blaxel.ai)), and created a first [workspace](Security/Workspace-access-control) (you’ll need the workspace ID).
Run the installer in a terminal:
```shell macOS and Linux theme={null}
curl -fsSL https://blaxel.ai/install.sh | sh
```
```powershell Windows theme={null}
irm https://blaxel.ai/install.ps1 | iex
```
The installer checks the Blaxel CLI (`bl`) against the release checksums, installs it, and adds it to your `PATH`. On macOS and Linux, it also installs completions for your current shell if you use zsh, bash, or fish. On Windows, [configure PowerShell completions](/cli-reference/introduction#shell-autocompletion) separately. It then runs [`bl setup`](/cli-reference/commands/bl_setup), which shows what it found and installs it when you press Enter: the [Blaxel skills](/skills-mcp#blaxel-skills) and MCP servers for the coding agents on your machine (Claude Code, Codex, Cursor and others), then the browser login. Run it again at any time to add what is missing.
```shell theme={null}
brew install blaxel-ai/blaxel/blaxel
bl setup
```
Homebrew 6.0.0+ requires explicit trust for non-official taps, so use the fully qualified formula name.
On macOS and Linux, set these variables for `sh`:
| Variable | Effect |
| - | - |
| `VERSION=v0.1.119` | Install that release instead of the latest |
| `BINDIR=/usr/local/bin` | Install to that folder instead of `~/.local/bin` |
| `BL_INSTALL_SETUP=false` | Install the CLI only, and run `bl setup` later |
| `BL_INSTALL_SKILLS=false` | Leave your coding agents alone |
| `BL_INSTALL_PATH=false` | Do not add `bl` to your `PATH` |
| `BL_INSTALL_COMPLETION=false` | Skip the shell completions |
For example, to install a specific release for all users:
```shell theme={null}
curl -fsSL https://blaxel.ai/install.sh | sudo VERSION=v0.1.119 BINDIR=/usr/local/bin sh
```
Under `sudo`, the installer still sets up your own shell, coding agents and login. In CI, it installs the CLI only and leaves the shell configuration and coding agents alone. On Windows, run `$env:BL_INSTALL_SETUP = "false"` before the installer to install the CLI only.
The installer logs you in. To log in again or to another workspace, open a terminal and run:
```bash theme={null}
bl login
```
The Blaxel SDK requires two environment variables to authenticate:
| Variable | Description |
| - | - |
| `BL_WORKSPACE` | Your Blaxel workspace name |
| `BL_API_KEY` | Your Blaxel API key |
You can create an API key from the [Blaxel console](https://app.blaxel.ai/profile/security). Your workspace name is visible in the URL when you log in to the console (e.g. `app.blaxel.ai/{workspace}`).
Set them as environment variables or add them to a `.env` file at the root of your project:
```bash theme={null}
export BL_WORKSPACE=my-workspace
export BL_API_KEY=my-api-key
```
When developing locally, you can also **log in to your workspace with Blaxel CLI** (as shown above). This allows you to run Blaxel SDK functions that will automatically connect to your workspace without additional setup. When you deploy on Blaxel, authentication is handled automatically — no environment variables needed.
Blaxel is the ultimate toolkit for AI agent builders. Our flagship product is the **sandbox**, although we also offer other services for the rest of your agentic workloads on our infrastructure.
Sandboxes are secure, instant-launching compute environments that you can use for running AI code. Blaxel lets you keep sandboxes on automatic standby with optimized agent hosting for near instant latency.
With Blaxel sandboxes, you get:
* Automatic scale-to-zero after 5s inactivity, resume from standby under 25ms even after weeks.
* Micro-VMs with full access to file system, processes and logs — and native support for Zero Data Retention (ZDR).
* Preview URLs with your own custom domain.
Sandboxes can be created programmatically using Blaxel SDK. Install the SDK:
```shell Python (pip) theme={null}
pip install blaxel
```
```shell Python (uv) theme={null}
uv pip install blaxel
```
```shell TypeScript (pnpm) theme={null}
pnpm install @blaxel/core
```
```shell TypeScript (npm) theme={null}
npm install @blaxel/core
```
```shell TypeScript (yarn) theme={null}
yarn add @blaxel/core
```
```shell TypeScript (bun) theme={null}
bun add @blaxel/core
```
Create your first sandbox:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
async function main() {
// Create a new sandbox
const sandbox = await SandboxInstance.createIfNotExists({
name: "my-sandbox",
image: "blaxel/base-image:latest", // public or custom image
memory: 4096, // in MB
ports: [{ target: 3000, protocol: "HTTP" }], // ports to expose
labels: { env: "dev", project: "my-project" }, // labels
region: "us-pdx-1" // deployment region
});
console.log("Sandbox created: ", {sandbox});
}
main().catch(console.error);
```
```python Python theme={null}
import asyncio
from blaxel.core import SandboxInstance
async def main():
# Create a new sandbox
sandbox = await SandboxInstance.create_if_not_exists({
"name": "my-sandbox",
"image": "blaxel/base-image:latest", # public or custom image
"memory": 4096, # in MB
"ports": [{ "target": 3000 }], # ports to expose
"labels": {"env": "dev", "project": "my-project"}, # labels
"region": "us-pdx-1" # deployment region
})
print(f"Sandbox created: {sandbox}")
if __name__ == "__main__":
asyncio.run(main())
```
Read the complete guide for creating & connecting to sandboxes using Blaxel SDK.
Or deploy another type of resource:
Deploy your agent’s components on a purpose-built computing infrastructure for agentics.
In Python, you will need to [have pip installed](https://pip.pypa.io/en/stable/installation/); in TypeScript, you will need to [have npm installed](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) for this.
Let’s initialize a first app. The following command initializes a new **pre-scaffolded local repository** ready for developing and deploying your agent on Blaxel.
```bash theme={null}
bl new agent
```
You can now develop your agent application using any framework you want (or none), and use the [Blaxel](sdk-reference/introduction) [SDK](sdk-reference) to [leverage other Blaxel deployments](Agents/Develop-an-agent). A placeholder HTTP API server is already preconfigured in `/src/main.py` or `/src/main.ts`.
In Python, you will need to [have pip installed](https://pip.pypa.io/en/stable/installation/); in TypeScript, you will need to [have npm installed](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) for this.
Let’s initialize a first batch job definition. The following command initializes a new **pre-scaffolded local repository** ready for developing and deploying your job on Blaxel.
```bash theme={null}
bl new job
```
You can now develop your job and use the [Blaxel](sdk-reference/introduction) [SDK](sdk-reference) to leverage other Blaxel deployments in `/src/index.py` or `/src/index.ts`.
In Python, you will need to [have pip installed](https://pip.pypa.io/en/stable/installation/); in TypeScript, you will need to [have npm installed](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) for this.
You can quickly **initialize a new MCP server from scratch** by using CLI command `bl new`. This initializes a new pre-scaffolded local repo where your entire code can be added.
```bash theme={null}
bl new mcp
```
You can now [develop your MCP server](Functions/Create-MCP-server) using the [Blaxel](sdk-reference/introduction) [SDK](sdk-reference) in `/src/server.py` or `/src/server.ts`.
Run the following command to serve your workload locally, replacing `<>` with the name of the workload's root directory:
```bash theme={null}
cd <>;
bl serve;
```
Query your agent locally by making a **POST** request to this endpoint: [`http://localhost:1338`](http://localhost:1338) with the following payload format: `{"inputs": "Hello world!"}`:
```bash theme={null}
curl -X POST -H "Content-Type: application/json" -d '{"inputs": "Hello world!"}' http://localhost:1338
```
Execute your job locally by making a **POST** request to this endpoint: [`http://localhost:1338`](http://localhost:1338) with the following payload format: `{"tasks": [{"param1": "value1"}]}`
```bash theme={null}
curl -X POST -H "Content-Type: application/json" -d '{"tasks": [{"name": "John"}, {"name": "Jane"}]}' http://localhost:1338
```
You can test it by running the following command which launches **both** the MCP server and a web application to query it ([MCP Inspector](https://github.com/modelcontextprotocol/inspector), managed by MCP) locally.
```shell TypeScript theme={null}
pnpm inspect
```
```shell Python theme={null}
BL_DEBUG=true uv run mcp dev src/server.py
```
The web application is accessible at: [http://127.0.0.1:6274](http://127.0.0.1:6274/). Alternatively, you can just simply [serve the server](Functions/Deploy-a-function) locally by running `bl serve --hotreload`.
To **push to Blaxel**, run the following command. Blaxel will handle the build and deployment:
```bash theme={null}
bl deploy
```
Your workload is made available as a **serverless auto scalable** **global endpoint** 🌎.
Run an inference from a terminal with the following command:
```bash theme={null}
bl chat <>
```
This gives you a chat-like interface where you can interact with your agent. To use this when serving locally, just add option `--local`.
Alternatively, **just call the** [global endpoint](Agents/Query-agents).
Trigger an execution of your batch job from a terminal by running:
```bash theme={null}
# Run a job using Blaxel CLI with --data argument
bl run job <> --data '{"tasks": [{"name": "John"}, {"name": "Jane"}]}'
```
Alternatively, **just call the** [global endpoint](Jobs/Overview).
When you deploy an MCP server on Blaxel, an **HTTP endpoint** is generated on Global Agentics Network to connect to the server.
The server endpoint looks like this:
```http Connect to an MCP server theme={null}
https://run.blaxel.ai/{YOUR-WORKSPACE}/functions/{YOUR-SERVER-NAME}/mcp
```
Read more about [how to connect to the server here](Functions/Invoke-functions).
## Next steps
You are ready to run AI with Blaxel! Here’s a curated list of guides which may be helpful for you to make the most of Blaxel, but feel free to explore the product on your own!
Let your AI coding assistant (Cursor, Claude Code, Windsurf…) create and manage Blaxel resources using natural language.
Complete guide for spawning sandboxed VMs for your agents to access.
Complete guide for creating and running batch jobs from your agents.
Complete guide for deploying AI agents on Blaxel.
Complete guide for managing deployment and routing policies on the Global Agentics Network.
## Questions?
Although we designed this documentation to be as comprehensive as possible, you are welcome to contact [support@blaxel.ai](mailto:support@blaxel.ai) or the community on [Discord](https://discord.gg/CsWKUZUHFQ) with any questions or feedback you have.
# Custom domains
Source: https://docs.blaxel.ai/Infrastructure/Custom-domains
Expose Blaxel agents, sandboxes, MCP servers, and other resources through your own custom domain names with managed TLS certificates.
Blaxel allows you to connect your own domain names to your Blaxel workspace to use when exposing resources.
You can also share a verified preview custom domain with another workspace in the same account. The target workspace can use the shared domain for its own previews, while the shared copy stays read-only.
When you register a domain, you also enable the use of wildcard subdomains for that domain. For example:
* If you register `mycompany.com`, you can use any `*.mycompany.com` subdomain to create custom URLs.
* If you register `preview.mycompany.com`, you can use any `*.preview.mycompany.com` subdomain to create custom URLs.
* And so on.
Each domain is uniquely assigned to a region. For example, if `preview.mycompany.com` is registered in region `us-pdx-1`, it is not possible to use it in region `eu-lon-1` (for example) or any other region.
In the [Blaxel Console](https://app.blaxel.ai/), navigate to **Network** > **Custom Domains** in the sidebar and add a new custom domain. Then, you'll need to configure DNS records with your domain provider. Finally, verify from within Blaxel: after DNS propagation completes, your custom domain will become fully usable.
## Share a custom domain
After you verify a preview custom domain, you can share it with another workspace in the same account.
* Use a workspace share when one target workspace should get a read-only copy
* Use an account share when every workspace in the account should be able to use the domain
* Create previews from the target workspace after the share is in place
Only verified preview custom domains can be shared.
When a target workspace binds a preview to the shared domain, the full host still needs to be unique across the account. If another workspace already uses the same `.` host, Blaxel returns `409`.
## Use a custom domain
Custom domains can currently be used for:
* [Sandbox previews](../Sandboxes/Preview-url)
* Agents: coming soon!
* MCP servers: coming soon!
Related pages:
Create preview URLs and configure fallback routing for custom domains.
Review recent platform updates related to custom domains and previews.
# Dedicated egress gateways
Source: https://docs.blaxel.ai/Infrastructure/Dedicated-egress-gateways
Attach dedicated egress IP addresses to Blaxel workloads so outbound traffic from sandboxes, agents, and jobs uses predictable, allowlistable IPs.
This feature is currently in private preview and is not intended for production use. In private preview, it has the following limitations:
* It is only available for sandboxes.
* Egress gateway assignment is only possible at sandbox creation time.
* Egress gateway removal is only possible by deleting the corresponding sandbox.
For more information, [contact us on Discord](https://discord.gg/CsWKUZUHFQ) or [request access](https://blaxel.ai/contact?message=I%20want%20to%20get%20preview%20access%20to%20dedicated%20egress%20IPs%20for%20the%20following%20use%20case%3A).
Dedicated egress gateways enable customers to exclusively attach IP addresses to sandboxes within their workspace. This makes it possible to whitelist outbound traffic from Blaxel's systems for use in downstream providers and eliminates the need for workarounds such as self-hosted proxy servers or VPNs.
A single egress IP address supports up to 32,000 sandboxes. It is also possible to group IP addresses into pools to automatically load-balance outbound traffic for high-demand workloads.
Sandboxes that do not have a dedicated egress gateway will use the shared [public IP addresses](./Regions#public-ip-addresses) instead.
## Order an egress gateway
Egress gateway configuration takes place from the [Blaxel Console](https://app.blaxel.ai).
* Navigate to **Network** > **Egress gateways**.
* Click the **Order IP** button in the top right corner.
* Select the quantity and egress gateway region.
Provisioning is automated and typically completes within a few minutes.
## Assign an egress gateway to a sandbox
Once provisioning is complete, you can attach the assigned egress gateway when creating one or more sandboxes. All outbound traffic from the attached sandboxes will then originate from this unique address.
You can assign an egress gateway to a sandbox at creation time via the Blaxel Console or via the Blaxel SDK.
### Blaxel Console
When creating a new sandbox via the Blaxel Console, use the **Egress IP** drop-down to assign an available egress gateway to that sandbox. Only egress gateways in the same region as the new sandbox are shown for assignment.
### Blaxel SDK
When creating a new sandbox via the Blaxel SDK, you can assign it to a dedicated egress gateway by passing a `network` option when creating the sandbox. Here is an example when creating a sandbox:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.create({
name: "my-sandbox",
image: "blaxel/base-image:latest",
memory: 4096,
region: "eu-fra-1",
network: {
egress: {
mode: "dedicated",
gateway: "egress-gateway-4"
}
},
});
```
```python Python theme={null}
import asyncio
from blaxel.core.sandbox import SandboxInstance
async def main():
sandbox = await SandboxInstance.create(
{
"name": "my-sandbox",
"image": "blaxel/base-image:latest",
"memory": 4096,
"region": "eu-fra-1",
"network": {
"egress": {
"mode": "dedicated",
"gateway": "egress-gateway-4",
},
},
}
)
asyncio.run(main())
```
# Generations
Source: https://docs.blaxel.ai/Infrastructure/Gens
Overview of Blaxel infrastructure generations from Mark 1 through Carbon.
Blaxel workloads run on one of several infrastructure generations, described below from most to least recent.
## Carbon (a.k.a. Mark 3.1)
Carbon is the newest Blaxel infrastructure generation. Currently in private preview, it's built directly as an evolution of Mark 3.0: same microVM foundation, same SDK, CLI, and REST surface. It supports many more features than 3.0, including a layer for manually snapshotting and forking sandboxes.
On Mark 3.0, a sandbox is already snapshotted automatically and invisibly whenever it goes into standby, which is what makes its near-instant resume possible. Carbon exposes that same underlying mechanism directly, so you can:
* **Manually snapshot** a sandbox's full memory state (filesystem and running processes) at any point in time, independent of the standby cycle, so you can roll back to that exact state later.
* **Fork a sandbox**, creating a new sandbox that inherits another one's exact memory state — either its current live state or a previously saved snapshot. Useful for branching a session, exploring several execution paths in parallel, or reproducing an issue from a known checkpoint.
* **Fork straight into a live application**, turning a sandbox (or a specific snapshot of one) into a new application revision, with canary traffic splitting and custom domain support, so agent-generated code can move from iteration to production without a separate build step.
See [Snapshots and forking](../Sandboxes/Fork) for the full API reference.
Carbon is currently in private preview and is rolling out progressively by region and by workspace, so it isn't yet the default everywhere. A few Mark 3.0 features aren't yet available together with Carbon:
* Agent Drive
* Firewalling
* Dedicated egress IPs
The API is otherwise identical between the two generations, so no code changes are needed to move a workload between them.
## Mark 3.0
By default, deployed workloads run on Mark 3.0 infrastructure. Mark 3.0 infrastructure leverages microVMs to run with critical-low cold-starts.
Mark 3.0 infrastructure is currently available to run the following workloads:
* [sandboxes](../Sandboxes/Overview)
* [agents](../Agents/Overview)
* [MCP servers](../Functions/Overview)
* [batch jobs](../Jobs/Overview)
## What about Mark 2
Mark 2 infrastructure used containers to run workloads, providing emulation of most Linux system calls. Cold starts typically took between 2 and 10 seconds. After a deployment was queried, it stayed warm for a period that varies based on overall infrastructure usage, allowing it to serve subsequent requests instantly.
Mark 2 infrastructure was suitable when:
* your workload required system calls not supported by Mk 3 infrastructure
* boot times of around 5 seconds were suitable for your needs
* your deployment received consistent traffic that keeps it running warm
* you needed to run workloads in specific regions for sovereignty or regulatory compliance using [deployment policies](../Model-Governance/Policies)
* you required revision control for rollbacks or canary deployments
Mark 2 infrastructure was deprecated in Q4 2025 and decommissioned in Q2 2026.
## What about Mark 1
Mark 1 infrastructure was originally designed for serverless ML model inference but proved inadequate for running agentic workloads. Built on Knative Custom Resource Definitions (CRDs) running atop managed Kubernetes clusters, it leveraged KNative Serving's scale-to-zero capabilities and Kubernetes' container orchestration features. The infrastructure utilized pod autoscaling through the Knative Autoscaler (KNA). It also allowed federating multiple clusters via a Blaxel agent that would offload inference requests from one Knative cluster to another based on a usage metric.
While it demonstrated reasonable stability even at 20+ requests per second and achieved satisfying cold starts through runtime optimization, its architecture wasn't suited for the more lightweight workloads that make up most of autonomous agents: tool calls, agent orchestration, and external model routing.
Mark 1 infrastructure was decommissioned in January 2025.
# Regions
Source: https://docs.blaxel.ai/Infrastructure/Regions
Available deployment regions for Blaxel infrastructure including Oregon, Virginia, London, and Frankfurt, with guidance on resource-specific region selection.
Blaxel is available in many regions across the globe. Some types of resources can be deployed ‘globally’ over multiple regions (for example: [agents](../Agents/Overview)), while some other types are deployed only in one specific region (for example: [sandboxes](../Sandboxes/Overview)).
This guide details the available regions for [Mark 3 infrastructure generation](/Infrastructure/Gens).
## Available regions
The following regions are available for [Mark 3 infrastructure generation](/Infrastructure/Gens):
| **Region code** | **Location** | **Country** |
| - | - | - |
| `us-pdx-1` | Oregon | United States |
| `us-was-1` | North Virginia | United States |
| `eu-lon-1` | London | United Kingdom |
| `eu-fra-1` | Frankfurt | Germany |
| `auto` | Automatic (your closest region) | - |
All regions are not necessarily available in self-service by default. Please [contact us](http://blaxel.ai/contact?message=I+would+like+to+get+access+to+region+XYZ+because+\&purpose=regions\&origin=docs) if you would like access to a specific region.
## Selecting a region for deployment
When deploying a resource on Blaxel, you sometimes have the option to choose the deployment region. This depends on the type of resource, as explained below.
For all resource types, the deployment region **is always subject to the** [deployment policies](../Model-Governance/Policies) applied to the resource or workspace. A resource will never be deployed in a region that is prohibited by an existing deployment policy.
### Resources which are globally distributed over multiple regions
This is the case of:
* [agents](../Agents/Overview)
* [MCP servers](../Functions/Overview)
* [batch jobs](../Jobs/Overview)
**When deploying**
For these types of resources, you don't need to specify a region during deployment. The resource becomes available across all regions allowed by applicable [deployment policies](../Model-Governance/Policies).
However, you can optionally **pin an agent or MCP server to a specific region** by setting the `region` field in your [`blaxel.toml`](/deployment-reference). This is required when you want to [attach volumes to an agent](../Agents/Deploy-an-agent#attach-volumes-to-an-agent), since volumes are regional resources and the agent must run in the same region.
```toml blaxel.toml theme={null}
region = "us-pdx-1"
```
**When requesting**
For these types of resources, you receive a global query endpoint that automatically routes to the optimal region as determined by Blaxel's Global Agentics Network.
### Resources which are deployed in one specific region
* [sandboxes](../Sandboxes/Overview) (and their [preview URLs](../Sandboxes/Preview-url))
**When deploying**
For these types of resources, you can **optionally specify** a region during deployment, provided it is allowed by applicable [deployment policies](../Model-Governance/Policies). If you do not specify a region, Blaxel will deploy the resource automatically on a default region determined to be optimal.
**When requesting**
For these types of resources, you receive a regional query endpoint (which contains the name of the region in the domain).
## Public IP addresses
If you need to whitelist traffic from your Blaxel deployments, you can retrieve the current [list of public IP addresses](/api-reference/publicips:list/list-public-ips#list-public-ips) used by Blaxel through the Blaxel API. IP addresses are returned as CIDR ranges per region, and the results list can also be filtered by adding a `region` parameter to the API query (only for [Mark 3 infrastructure](/Infrastructure/Gens#mark-3-infrastructure)).
Here is an example API request:
```shell theme={null}
curl --request GET \
--url https://api.blaxel.ai/v0/publicIps?region=us-was-1 \
--header 'Authorization: Bearer '
```
# Integrations
Source: https://docs.blaxel.ai/Integrations
Browse Blaxel integrations that connect your agents to LLM providers, SaaS apps, databases, and private APIs through pre-built MCP servers.
**Blaxel Integrations** enable you to let Blaxel resources access various external APIs, private networks and AI services, and to connect to downstream interfaces such as your applications. With integrations, you can **manage access control, credentials, and observability** across different providers and systems from a single platform.
Blaxel supports integration with:
* LLM providers like OpenAI or Anthropic
* APIs and SaaS for agents’ tools like Slack or GitHub
* gateways for tools/agents
* downstream applications
Connections in the workspace settings must be configured by a [workspace admin](Security/Workspace-access-control) before they can be used by team members. Agent frameworks and tools have their own setup requirements in the linked guides.
[oauth-flow](Functions/oauth-flow)
## All integrations
### LLM APIs
These integrations let you connect your agents to LLMs from top providers, while controlling access and cost.
[OpenAI](Integrations/OpenAI)
[Anthropic](Integrations/Anthropic)
[MistralAI](Integrations/MistralAI)
[Cohere](Integrations/Cohere)
[xAI](Integrations/xAI)
[DeepSeek](Integrations/DeepSeek)
[Azure-AI-Foundry](Integrations/Azure-AI-Foundry)
[HuggingFace](Integrations/HuggingFace)
**AWS Bedrock**
**Gemini**
**Google Vertex AI**
### Tools and APIs
These integrations let you equip your agents with tools to access APIs, SaaS and databases.
[**AgentMail**](https://agentmail.to/)
[**Airweave**](https://airweave.ai/)
**AWS S3**
**AWS SES**
**Brave Search**
**Cloudflare**
**Dall-E**
**Discord**
**Exa**
**GitHub**
**GitLab**
[Google-Maps](Integrations/Google-Maps)
[Gmail](Integrations/Gmail)
**HubSpot**
**Linear**
**Linkup**
[**Nia**](https://app.trynia.ai/sign-in?redirect_url=https://app.trynia.ai/)
**Notion**
**PostgreSQL**
**Qdrant**
**Sendgrid**
**Shopify**
**Slack**
**Smartlead**
**Snowflake**
**Supabase**
**Tavily**
**Telegram**
**Trello**
**Twilio**
### Frameworks
Blaxel lets you bring agents developed in many of the most popular AI agent frameworks, optimizing how your agent builds and runs no matter how you coded it.
[OpenClaw](Tutorials/OpenClaw)
[Claude Agent SDK](Tutorials/Claude-Agent-SDK-Index)
[Claude Managed Agents](/Tutorials/Claude-Managed-Agents)
[CrewAI](Tutorials/CrewAI)
[Google ADK](Tutorials/ADK)
[LangChain](Tutorials/LangChain)
[LlamaIndex](Tutorials/LlamaIndex)
[Mastra](Tutorials/Mastra)
[n8n](Tutorials/n8n)
[OpenAI Agents SDK](Tutorials/OpenAI-Agents-SDK-Index)
[OpenAI Agents API](/Integrations/OpenAI-Agents-API)
[PydanticAI](Tutorials/PydanticAI)
[Vercel AI SDK](Tutorials/Vercel-AI)
[Custom agents](Tutorials/Custom-Agents)
### Integrate in your applications
These integrations let you **expose or use Blaxel resources** in downstream applications, gateways and marketplaces.
[AgentMail](Integrations/AgentMail)
[Rippletide](Integrations/Rippletide)
# AgentMail
Source: https://docs.blaxel.ai/Integrations/AgentMail
Use AgentMail with Blaxel to build an intelligent assistant that autonomously answers technical questions over email through agentic inboxes.
This example shows how to integrate agentic inbox provider [AgentMail](https://agentmail.to/) with Blaxel to create a customer success agent that autonomously handles technical questions via email (inbound and outbound).
## Requirements
To use this example, you'll need:
* the unique username for your AgentMail inbox
* an AgentMail API key
## Create an AgentMail inbox and API key
From your AgentMail dashboard, create a new inbox, providing a unique username (by default, the domain is set to `agentmail.to`):
From the same dashboard, obtain an API key:
## Configure the project
Clone the [example repository](https://github.com/blaxel-templates/template-agentmail-docbot) and follow the instructions provided.
Note that you must configure the following environment variables for your deployment:
```bash theme={null}
INBOX_USERNAME= # a unique username for your inbox
AGENTMAIL_API_KEY= # your AgentMail API key
```
Create a `.env` file in the project root with these variables, or set them in your deployment environment.
The `AGENTMAIL_API_KEY` variable is required. If it is missing or not correctly set, the AgentMail client will fail to authenticate and return a **403 Forbidden** error at runtime. Confirm this variable is configured before deploying.
## Deploy the agent
[Deploy the agent](../Agents/Deploy-an-agent) on Blaxel. Once the agent is deployed, retrieve its [inference endpoint](../Agents/Query-agents).
You will need an API key to query the agent externally. Follow [this guide to create a service account in your workspace and generate an API key](../Security/Service-accounts).
Complete documentation for using AgentMail.
# Anthropic integration
Source: https://docs.blaxel.ai/Integrations/Anthropic
Set up the Anthropic integration on Blaxel to call Claude models through a unified endpoint with centralized access control and observability.
The Anthropic integration allows Blaxel users to **call Anthropic models using a Blaxel endpoint** in order to unify access control, credentials and observability management.
The integration must be set up by an [admin](../Security/Workspace-access-control) in the Integrations section in the [workspace settings](../Security/Workspace-access-control).
## Set up the integration
In order to use this integration, you must register an Anthropic access token into your Blaxel workspace settings. The scope of this access token (i.e. the Anthropic workspace it is allowed to access) will be the scope that Blaxel has access to.
First, generate an [Anthropic API key](https://docs.anthropic.com/en/api/getting-started) from [your Anthropic organization settings](https://console.anthropic.com/settings/keys). Select the workspace to use for this key.
On Blaxel, in Workspace Settings > Anthropic integration, create a new connection and paste this token into the “API key” section.
## Connect to an Anthropic model
Once you’ve set up the integration in the workspace, any workspace member can use it to reference an Anthropic model as an [external model API](../Models/External-model-apis).
When creating a model API, select Anthropic. You can search for any model from the Anthropic catalog.
After the model API is created, you will receive a dedicated global Blaxel endpoint to call the model. Blaxel will forward inference requests to Anthropic, using your Anthropic credentials for authentication and authorization.
Because your own credentials are used, any inference request on this endpoint will incur potential costs on your Anthropic account, as if you queried the model directly on Anthropic.
# Azure AI Foundry integration
Source: https://docs.blaxel.ai/Integrations/Azure-AI-Foundry
Connect your Blaxel agents to LLMs deployed in Azure AI Inference, Azure OpenAI Service, and Azure AI Services through the Foundry integration.
The Azure AI Foundry integration allows Blaxel users to **call model deployments from [Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-studio/what-is-ai-studio) services** (Azure AI Inference, Azure OpenAI Service, and Azure AI Services) through a Blaxel endpoint that unifies access control, credentials and observability management.
There are 2 types of integrations related to this service:
* **Azure AI Inference**: connect to a model endpoint deployed as an “Azure AI Services” model on Azure. This typically includes OpenAI models.
* **Azure AI Marketplace**: connect to a model deployed from the Azure Marketplace. This typically includes Llama models.
The integration must be set up by an [admin](../Security/Workspace-access-control) in the Integrations section in the [workspace settings](../Security/Workspace-access-control).
## Azure AI Inference
### Set up the integration
In order to use this integration, you must register an Azure AI Inference endpoint and access key into your Blaxel workspace settings.
First, go to the [Azure AI Foundry console](https://ai.azure.com/build/overview), and open your project. Select the “Azure AI Inference” capability, and retrieve both:
* the **API key**
* the **Azure AI model inference endpoint**
On Blaxel, in Workspace Settings > Azure AI Inference integration, create a new connection and paste the endpoint and the Access token there.
### Connect to a model
Once you’ve set up the integration in the workspace, any workspace member can use it to reference an “Azure AI Services” model as an [external model API](../Models/External-model-apis).
When creating a model API, select “Azure AI Inference”. Then, input the **name** of your model just as it is deployed on Azure.
After the model API is created, you will receive a dedicated global Blaxel endpoint to call the model. Blaxel will forward inference requests to Azure, using your Azure credentials for authentication and authorization.
Because your own credentials are used, any inference request on this endpoint will incur potential costs on your Azure account, as if you queried the model directly on Azure.
## Azure AI Marketplace
### Set up the integration & connect to a model
In order to use this integration, you must register an Azure endpoint and access token into your Blaxel workspace settings.
The difference with most other model API integrations is that the integration will be tied to your model.
First, go to the [Azure AI Foundry console](https://ai.azure.com/build/overview), and open your project. Go to your models, and open the model you want to connect to. Retrieve:
* the **API key**
* the **Azure AI model inference endpoint**
On Blaxel, in Workspace Settings > Azure Marketplace integration, create a new connection and paste this token into the “Access token” section.
Once you’ve set up the integration in the workspace, any workspace member can use it to connect to the model as an [external model API](../Models/External-model-apis).
When creating a model API, select Azure Marketplace, input the name of the endpoint as you want it on Blaxel, and finish creating the model.
After the model API is created, you will receive a dedicated global Blaxel endpoint to call the model. Blaxel will forward inference requests to Azure, using your Azure credentials for authentication and authorization.
Because your own credentials are used, any inference request on this endpoint will incur potential costs on your Azure account, as if you queried the model directly on Azure.
# Cohere integration
Source: https://docs.blaxel.ai/Integrations/Cohere
Set up the Cohere integration on Blaxel to call Cohere models through a unified endpoint with centralized access control and observability.
The Cohere integration allows Blaxel users to **call Cohere models using a Blaxel endpoint** in order to unify access control, credentials and observability management.
The integration must be set up by an [admin](../Security/Workspace-access-control) in the Integrations section in the [workspace settings](../Security/Workspace-access-control).
## Set up the integration
In order to use this integration, you must register a Cohere access token into your Blaxel workspace settings. First, generate a [Cohere API key](https://docs.cohere.com/v2/docs/rate-limits) from [the Cohere platform](https://dashboard.cohere.com/api-keys).
On Blaxel, in Workspace Settings > Cohere integration, create a new connection and paste this token into the “API key” section.
## Connect to a Cohere model
Once you’ve set up the integration in the workspace, any workspace member can use it to reference a Cohere model as an [external model API](../Models/External-model-apis).
When creating a model API, select Cohere. You can search for any model from the Cohere catalog.
After the model API is created, you will receive a dedicated global Blaxel endpoint to call the model. Blaxel will forward inference requests to Cohere, using your Cohere credentials for authentication and authorization.
Because your own credentials are used, any inference request on this endpoint will incur potential costs on your Cohere account, as if you queried the model directly on Cohere.
# DeepSeek integration
Source: https://docs.blaxel.ai/Integrations/DeepSeek
Set up the DeepSeek integration on Blaxel to call DeepSeek models through a unified endpoint with centralized access control and observability.
The DeepSeek integration allows Blaxel users to **call DeepSeek models using a Blaxel endpoint** in order to unify access control, credentials and observability management.
The integration must be set up by an [admin](../Security/Workspace-access-control) in the Integrations section in the [workspace settings](../Security/Workspace-access-control).
## Set up the integration
In order to use this integration, you must register a DeepSeek access token into your Blaxel workspace settings. First, generate a DeepSeek API key from [your DeepSeek console](https://platform.deepseek.com/api_keys).
On Blaxel, in Workspace Settings > DeepSeek integration, create a new connection and paste this token into the “API key” section.
## Connect to a DeepSeek model
Once you’ve set up the integration in the workspace, any workspace member can use it to reference a DeepSeek model as an [external model API](../Models/External-model-apis).
When creating a model API, select DeepSeek. You can search for any model from the DeepSeek catalog.
After the model API is created, you will receive a dedicated global Blaxel endpoint to call the model. Blaxel will forward inference requests to DeepSeek, using your DeepSeek credentials for authentication and authorization.
Because your own credentials are used, any inference request on this endpoint will incur potential costs on your DeepSeek account, as if you queried the model directly on DeepSeek.
# Gmail
Source: https://docs.blaxel.ai/Integrations/Gmail
Connect Gmail to your Blaxel agents to give them email communication capabilities, including reading, drafting, and sending messages.
The *Gmail integration* allows you to equip your agents with tools to interact with Gmail services, enabling email communication and message processing within your applications.
## Set up the integration
In order to use this integration, you must sign in to your Google account. This will create an integration to Gmail in your workspace settings.
### Single tenant
Create the integration yourself from Blaxel Console.
### Multi-tenant
Follow [this guide](/Functions/oauth-flow) to learn how to automate the creation of dedicated Gmail integrations through a user-facing OAuth flow.
Create integrations for your users with OAuth.
## Create a Gmail function
Once you’ve set up the integration in the workspace, any workspace member can use it to create a Gmail [function](../Functions/Overview).
When creating a function, select Gmail. After the function is created, you will receive a dedicated global Blaxel endpoint to call it.
### Available tools
This integration provides the following tools:
* `send_email`: Send an email using Gmail. No sender will be needed, it uses a default configuration. You just have to set the recipient as an email string address inside the parameter "to", a subject and a body in string format.
# Google Maps
Source: https://docs.blaxel.ai/Integrations/Google-Maps
Connect the Google Maps API to your Blaxel agents to enable geocoding, directions, place search, and other location-based capabilities.
The *Google Maps integration* lets you equip your agents with tools to interact with Google Maps services, enabling location-based functionalities and geographic data processing within your applications.
## Set up the integration
In order to use this integration, you must register a Google Cloud Platform (GCP) API access token. The scope of this access token will be the scope that Blaxel has access to.
First, generate a GCP API key from [your GCP 'API & Services' section](https://console.cloud.google.com/apis/credentials). Then on Blaxel, in the workspace settings, in the *Google Maps* integration, paste this token into the “API key” section.
## Create a Google Maps function
Once you’ve set up the integration in the workspace, any workspace member can use it to create a Google Maps [function](../Functions/Overview).
When creating a function, select Google Maps. After the function is created, you will receive a dedicated global Blaxel endpoint to call it.
### Available tools
This integration provides the following tools:
* `maps_geocode`: Convert an address into geographic coordinates
* `maps_reverse_geocode`: Convert coordinates into an address
* `maps_search_places`: Search for places using Google Places API
* `maps_place_details`: Get detailed information about a specific place
* `maps_distance_matrix`: Calculate travel distance and time for multiple origins and destinations
* `maps_elevation`: Get elevation data for locations on the earth
* `maps_directions`: Get directions between two points
# HuggingFace integration
Source: https://docs.blaxel.ai/Integrations/HuggingFace
Deploy public or private AI models from Hugging Face on Blaxel and serve them as auto-scalable inference endpoints for your agents.
The [HuggingFace](https://huggingface.co/) integration enables Blaxel users to **connect to [serverless endpoints](https://huggingface.co/docs/api-inference/en/index) from HuggingFace**—whether public, gated, or private—directly through their agents on Blaxel. The integration is bidirectional, letting you create new [deployments](https://huggingface.co/docs/inference-endpoints/index) on HuggingFace from the Blaxel console to use as [model APIs](../Models/External-model-apis).
The integration must be set up by an [admin](../Security/Workspace-access-control) in the Integrations section in the [workspace settings](../Security/Workspace-access-control).
## Set up the integration
In order to use this integration, you must register a HuggingFace access token into your Blaxel workspace settings. The scope of this access token (i.e. the HuggingFace resources it is allowed to access) will be the scope that Blaxel has access to.
First, generate a [HuggingFace access token](https://huggingface.co/docs/hub/security-tokens) from [your HuggingFace settings](https://huggingface.co/settings/tokens). Give this access token the scope that you want Blaxel to access on HuggingFace (e.g. repositories, etc.).
On Blaxel, in the workspace settings, in the *HuggingFace* integration, paste this token into the “API key” section.
## Connect to a HuggingFace model
Once you’ve set up the integration in the workspace, any workspace member can use it to reference a HuggingFace model as an [external model API](../Models/External-model-apis).
### Public and private models
When [creating a model API](../Models/Overview) on Blaxel, select “HuggingFace”. You can search for:
* any **public model** from [Inference API (serverless)](https://huggingface.co/docs/api-inference/index)
* any **private model** from [Inference Endpoints (dedicated)](https://huggingface.co/docs/inference-endpoints/index) in the organizations & repositories allowed by the integration’s token.
After the model API is created, you will receive a dedicated global Blaxel endpoint to call the model. Blaxel will forward inference requests to HuggingFace, using your HuggingFace credentials for authentication and authorization.
### Gated models
If the model you're trying to connect to is [gated](https://huggingface.co/docs/hub/models-gated), you'll **first need to request access on HuggingFace,** and accept their terms and conditions of usage (if applicable). Access to some HuggingFace models is granted immediately after request, while others require manual approval.
When the model gets deployed, Blaxel will check if the **integration token is allowed access to the model** on HuggingFace. If you have not been allowed access, the model deployment will fail in error.
## Create a HuggingFace Inference Endpoint
You can deploy a model in HuggingFace’s [Inference Endpoints](https://huggingface.co/docs/inference-endpoints/index) directly from the Blaxel console when creating a new [external model API](../Models/External-model-apis).
* **Organization**: select the HuggingFace namespace in which the endpoint will be deployed
* **Model**: select the model to deploy
* **Instance**: choose the type (GPU) and size of the instance to use for the deployment. Blaxel will trigger a deployment on Google Cloud Platform with default auto-scaling parameters.
* **Endpoint**: enter the name for your endpoint on HuggingFace
This action will incur costs on your HuggingFace subscription, depending on the choice of instance selected.
Once you launch a deployment, it will be available in your HuggingFace console, as well as your Blaxel console. You will receive a dedicated global Blaxel endpoint to call the model which proxies the requests to the HuggingFace endpoint and enforces token usage control and observability.
# Mistral AI integration
Source: https://docs.blaxel.ai/Integrations/MistralAI
Set up the Mistral AI integration on Blaxel to call Mistral models through a unified endpoint with centralized access control and observability.
The Mistral AI integration allows Blaxel users to **call Mistral AI models using a Blaxel endpoint** in order to unify access control, credentials and observability management.
The integration must be set up by an [admin](../Security/Workspace-access-control) in the Integrations section in the [workspace settings](../Security/Workspace-access-control).
## Set up the integration
In order to use this integration, you must register a Mistral AI access token into your Blaxel workspace settings. First, generate a Mistral AI API key from [your Mistral AI La Plateforme settings](https://console.mistral.ai/api-keys/).
On Blaxel, in Mistral AI integration, create a new connection and paste this token into the “API key” section.
## Connect to a Mistral AI model
Once you’ve set up the integration in the workspace, any workspace member can use it to reference a Mistral AI model as an [external model API](../Models/External-model-apis).
When creating a model API, select Mistral AI. You can search for any model from the Mistral AI catalog.
After the model API is created, you will receive a dedicated global Blaxel endpoint to call the model. Blaxel will forward inference requests to Mistral AI, using your Mistral AI credentials for authentication and authorization.
Because your own credentials are used, any inference request on this endpoint will incur potential costs on your Mistral AI account, as if you queried the model directly on Mistral AI.
# OpenAI integration
Source: https://docs.blaxel.ai/Integrations/OpenAI
Set up the OpenAI integration on Blaxel to call OpenAI models through a unified endpoint with centralized access control and observability.
The OpenAI integration allows Blaxel users to **call OpenAI models using a Blaxel endpoint** in order to unify access control, credentials and observability management.
To connect an OpenAI-hosted agent to a Blaxel Sandbox, see [OpenAI Agents API](/Integrations/OpenAI-Agents-API). This page covers OpenAI model access through Model Gateway.
The integration must be set up by an [admin](../Security/Workspace-access-control) in the Integrations section in the [workspace settings](../Security/Workspace-access-control).
## Set up the integration
In order to use this integration, you must register an OpenAI access token into your Blaxel workspace settings. The scope of this access token (i.e. the OpenAI resources it is allowed to access) will be the scope that Blaxel has access to.
First, generate an [OpenAI API key](https://platform.openai.com/docs/api-reference/authentication) from [your OpenAI Platform settings](https://platform.openai.com/api-keys). Set this API key in `Read-only` mode.
On Blaxel, in Workspace Settings > OpenAI integration, create a new connection and paste this token into the “API key” section.
## Connect to an OpenAI model
Once you’ve set up the integration in the workspace, any workspace member can use it to reference an OpenAI model as an [external model API](../Models/External-model-apis).
When creating a model API, select OpenAI. You can search for any model from the OpenAI catalog.
After the model API is created, you will receive a dedicated global Blaxel endpoint to call the model. Blaxel will forward inference requests to OpenAI, using your OpenAI credentials for authentication and authorization.
Because your own credentials are used, any inference request on this endpoint will incur potential costs on your OpenAI account, as if you queried the model directly on OpenAI.
# OpenAI Agents API on Blaxel
Source: https://docs.blaxel.ai/Integrations/OpenAI-Agents-API
Connect OpenAI-hosted agents to Blaxel computers, retain useful files with Agent Drive, and choose the cookbook execution mode for your application.
Give an OpenAI-hosted agent a Blaxel computer to turn a source report into a summary. The cookbook saves a verified local result and optionally uses Agent Drive so a fresh session can review the work after the first computer is deleted.
The Agents API is a public beta. The [cookbook](https://github.com/blaxel-ai/blaxel-openai-agents-api-cookbook) uses the public `openai` package and `client.beta.agents`; private GitHub access is not required.
## Architecture
OpenAI manages the agent, model calls, and session state. Blaxel supplies the computer that runs its commands, plus storage and controls for that computer. Your application defines the task, agent instructions, and resource lifecycle.
The cookbook connects `codex exec-server` in each worker Sandbox to the session's self-hosted environment. That connection is outbound, so the worker does not need a public inbound port.
```mermaid actions={false} theme={null}
flowchart LR
App["Your script"] -->|"create session and submit work"| API["OpenAI Agents API"]
App -->|"create computer"| Worker["Blaxel Sandbox"]
Worker -->|"outbound executor connection"| API
Worker --- Drive[("Agent Drive when enabled")]
Worker -->|"verified file"| Output["Local summary.md"]
```
The baseline is application-managed: the script starts one computer, runs the file task, verifies the result, and deletes its session and computer. The optional handoff starts a fresh pair only after the first has been deleted. Follow the [tutorial](/Tutorials/OpenAI-Agents-API) for that reader journey.
Webhook-managed execution is an optional extension. A controller starts or reconnects a worker when OpenAI requests an environment. Its [separate cookbook guide](https://github.com/blaxel-ai/blaxel-openai-agents-api-cookbook/blob/main/webhook/README.md) covers deployment and registration.
## Credentials and access
The cookbook requires Python 3.11 through 3.14, Git, OpenAI Agents API access, and a Blaxel workspace. Application-managed examples accept `BL_WORKSPACE` and `BL_API_KEY`, or an [installed Blaxel CLI](/cli-reference/introduction) with a `bl login` session. The deployed handler requires `BL_WORKSPACE` and `BL_API_KEY` because it creates workers after your local process exits.
| Credential | Used by | Purpose |
| - | - | - |
| `OPENAI_API_KEY` | Your application and the webhook controller | Create agents and sessions, submit work, and retrieve session state |
| `OPENAI_EXECUTOR_API_KEY` | Worker Sandboxes | Authenticate the executor's connection to OpenAI |
| `BL_WORKSPACE` and `BL_API_KEY` | Your application or webhook controller | Create and manage resources in the selected Blaxel workspace |
| `OPENAI_WEBHOOK_SECRET` | Webhook controller | Verify incoming OpenAI webhook signatures |
Create `OPENAI_API_KEY` on the [API keys page](https://platform.openai.com/api-keys) with Agents read/write and Responses write permissions. Create a separate `OPENAI_EXECUTOR_API_KEY` on the [Agents environment-key page](https://platform.openai.com/agents?tab=environments\&environment_view=keys), in the same organization, project, and user or service account as the session. Set all other permissions to None. The environment key permits connecting the executor only; a generic restricted API key with List models access does not establish that permission.
Both keys are required. Missing or identical keys fail before a worker is provisioned. The application key stays outside workers; only the environment key is passed as `CODEX_API_KEY` to `codex exec-server`. Follow the [OpenAI self-hosted setup](https://developers.openai.com/api/docs/guides/agents-api/environments/self-hosted#authentication) for the current dashboard flow.
This integration connects directly to the Agents API. It does not require a Model Gateway connection in Blaxel's OpenAI integration settings.
## Persistent files
The baseline saves a verified local copy in `outputs//summary.md`. Handoff saves `review.md` beside it. These copies let the reader inspect the result after temporary resources are removed.
Agent Drive mounts durable files at `/workspace/context` in the cookbook. Those files survive deletion of the Sandbox that wrote them. Unmounted Sandbox files remain temporary.
| Workflow | What continues | Drive scope |
| - | - | - |
| Fresh-session handoff | A new session reads `summary.md` and writes `review.md` after the first session and Sandbox are deleted | One Drive shared by the trusted handoff |
| Parallel agent team | Engineering and support specialists write separate findings; a coordinator reads both and writes `plan.md` | A unique Drive and workload label for each team run |
| Worker replacement | The same OpenAI session reconnects to a fresh worker and reads files from its previous worker | A separate Drive and matching access labels for each session |
The team example uses Python to schedule two specialists in parallel and wait for their verified outputs before submitting the coordinator's task. Agent Drive stores their files; it does not schedule tasks or transfer conversation history or model memory.
Use [Agent Drive permissions](/Agent-drive/Permissions) to define which workloads can access each Drive and path. A mount subdirectory alone does not isolate unrelated tenants or sessions.
## Webhook lifecycle
The cookbook includes a handler deployed in a controller Sandbox with a public webhook endpoint. The first deployment saves its agent ID and controller identity in `.runs/deployment-.json`. Later deployments and reconnection reuse that saved identity; `OPENAI_AGENT_ID` can select an existing saved agent explicitly. Register the handler's URL in your OpenAI project for `agent.session.action_required` and `agent.session.failed`.
* The handler verifies each signature and accepts environment work only for its configured `OPENAI_AGENT_ID`.
* On an `agent.session.action_required` delivery with an `environment_connection` action, it stores the session ID in SQLite, acknowledges the delivery, and re-reads the session before provisioning.
* One worker serves each session. The worker connects outbound to OpenAI, and the pending input continues without being resubmitted.
* The handler keeps the worker awake while its executor is connected. It does not delete a worker when the session becomes `idle`.
* A failed session triggers worker cleanup. Deleting a session sends no cleanup webhook, so your application must also delete its worker.
To release a worker deliberately, stop its executor before deleting the Sandbox. Wait for OpenAI's `agent.session.environment.disconnected` event before sending the next input. OpenAI can then request a replacement worker, which mounts the same session's Drive.
Sending input before OpenAI reports the environment disconnected can leave the turn without file access and does not trigger the provisioning webhook. Wait for the event rather than assuming deletion has already updated the OpenAI session.
The controller's SQLite queue survives process restarts inside that Sandbox. Deleting or expiring the controller loses the local queue.
## Configuration and limits
These are cookbook defaults and behavior, not platform-wide limits.
| Setting | Behavior |
| - | - |
| `BL_AGENT_DRIVE_MODE` | `auto` uses Agent Drive when available; `required` stops when it is unavailable; `off` uses temporary storage |
| Region | Agent Drive requires `us-was-1`, the default for both application-managed runs and webhook deployment |
| Application-managed Sandbox lifetime | 15 minutes from creation |
| `WORKER_TTL` | Webhook workers default to `2h` from creation |
| `CONTROLLER_TTL` | The controller defaults to `24h` from creation; restarting its process does not renew this lifetime |
| Resource namespace | `OPENAI_WEBHOOK_RESOURCE_PREFIX` selects a separate controller and worker namespace; use the same prefix for deployment and reconnection |
| Client and executor | The public client range is `openai>=3.13.0,<4`; Codex uses the prescribed `alpha` npm tag. Update dependencies deliberately and rerun the lifecycle checks; the README records verified versions |
The baseline can complete with temporary storage when Agent Drive access is unavailable. The fresh-session handoff, team example, and file-preserving reconnection check require Agent Drive. With temporary storage, a replacement worker starts with an empty filesystem.
The examples verify a completed turn and durable idle state before reading the output file. They submit each task once, reject concurrent input to the same session, and use bounded waits: 180 seconds for application-managed turns and 600 seconds for webhook-managed turns, including worker setup.
## Examples and cleanup
The [cookbook README](https://github.com/blaxel-ai/blaxel-openai-agents-api-cookbook#run-it-yourself) provides installation and credential setup. Start with the baseline, then try the optional handoff. The remaining commands are separate advanced examples:
| Command | Result |
| - | - |
| `./run.sh` | One agent reads the sample brief and creates a verified `summary.md` |
| `./run.sh --handoff` | Runs both stages: a summary, first-pair cleanup, then a fresh session that creates `review.md` |
| `.venv/bin/python -m examples.openai_agent_drive` | Two specialists and a coordinator create a shared plan |
| `.venv/bin/python -m examples.agent_drive` | A storage-only handoff verifies the same file in a replacement Sandbox without invoking a model |
| `./run.sh --deploy-webhook` | Deploys the controller; run it again after registering the URL and exporting its signing secret |
| `./run.sh --reconnect` | Verifies that a replacement worker in the same session reads the first worker's file |
These examples create hosted resources, and the agent examples invoke a model. Check their printed cleanup results: the scripts delete temporary sessions and workers, while Agent Drive is retained deliberately. The reconnect example also retains the controller and webhook registration.
When a deployment is no longer needed, remove its OpenAI webhook, controller, remaining workers, and API sessions. Inspect or export retained files before deleting their Drive, and use the configured resource prefix to avoid removing another deployment's resources.
Temporary resources are recorded immediately in `.runs/.json`. Receipts distinguish created/reused ownership, deletion requested, deletion verified, intentional retention, and cleanup failure. Preserve the receipt if the process is interrupted:
```bash theme={null}
.venv/bin/python cleanup_run.py .runs/.json
```
The recovery command acts on the exact owned sessions and workers in that receipt and preserves Drives. Inspect or export retained files before deleting their named Drive.
For a webhook deployment, keep the same `OPENAI_WEBHOOK_RESOURCE_PREFIX` and inspect or tear down its recorded resources:
```bash theme={null}
.venv/bin/python -m webhook.deploy --inspect
.venv/bin/python -m webhook.deploy --teardown
```
The deployment manifest binds recovery to the original Blaxel workspace and API endpoint. A target mismatch stops cleanup. The controller records the workers and Drives it actually allocates, including resources left by partial setup; teardown stops provisioning before reading that inventory. Reused resources and application-owned OpenAI sessions are preserved. Before removing the controller, teardown saves its final inventory locally so a later cleanup attempt can resume. If the controller expires before that inventory is captured, automatic teardown stops because ownership cannot be recovered safely.
Teardown retains Drives by default. Add `--delete-drives` only when you intend to remove the deployment's recorded owned Drives and their files.
OpenAI webhook registration is a separate dashboard resource. Export `OPENAI_WEBHOOK_ID` when deploying to record its identity. Teardown reports manual registration removal as pending until you remove that exact registration in the OpenAI dashboard and record your confirmation:
```bash theme={null}
.venv/bin/python -m webhook.deploy --confirm-registration-removed
```
This command records your dashboard verification; it does not delete the registration through an API. Keep the signing secret in the environment, never in the manifest.
## Troubleshooting
| Error or symptom | Cause and action |
| - | - |
| `invalid_beta`, missing beta header, or `agent_api_sdk` import error | Install the public dependencies from the current cookbook with `python -m pip install -e '.[dev]'`. Public `openai` supplies `OpenAI-Beta: agents=v1`; raw HTTP clients must supply it too. |
| Unknown `session.input.message` event | Use the current public client and `agent.session.input.message`. Update the checkout; do not patch generated SDK files. |
| Executor `403` or missing `api.agents.environments.connect` | Create an environment key on the Agents dashboard. Confirm the same organization, project, and owner as the application key. Do not substitute the application key. |
| Exported Blaxel key fails while `bl login` works | The deployed controller uses the exported key. Replace it with a valid key for `BL_WORKSPACE`; a local login does not prove that key works. |
| Executor exits or installation fails | Check the reported worker, process status, and bounded logs. Verify Node/npm, Codex, ripgrep, outbound registration, and WebSocket access. |
| Connection or turn deadline expires | Inspect the exact session and controller/worker IDs. OpenAI allows five minutes for an input-time connection; an expired request does not replay itself when a worker connects later. Do not blindly resend uncertain input. |
| Agent Drive unavailable or wrong region | Request access at the printed Console URL, use `us-was-1`, or choose `BL_AGENT_DRIVE_MODE=off` for the baseline. Handoff, team, and file-preserving reconnect require Drive access. Auth and mount errors are not fallback cases. |
| Webhook returns `503` | Register the deployment URL and export its signing secret before redeploying. Controller health alone does not prove signing is configured. |
| No worker after submitting webhook work | Confirm the saved agent, registration events, signature status, delivery history, and queued/failed jobs on this deployment. Successful local signing is not proof of an OpenAI-origin delivery. |
| Turn is `failed`, `cancelled`, or idle without a new completed turn | Inspect the original error. Idle alone is not success; require the current turn and independently verified file. |
| Cleanup `409` | Retry only the bounded cancellation/cleanup path for the exact receipt. If `no durably bound CCA root` persists, retain the session/request IDs for OpenAI support; do not start an extra model turn as a deletion workaround. |
| Interrupted process | Use its exact `.runs` receipt to inspect and clean up owned resources. Verify session 404 and worker absence or `TERMINATED`; a deletion request or TTL is not proof. |
Create a summary, open the result, and let a fresh agent review the saved file.Run the examples and inspect their lifecycle, verification, and cleanup code.Configure durable files and access permissions.Configure the computer, network controls, and resource lifetime.
# Rippletide
Source: https://docs.blaxel.ai/Integrations/Rippletide
Combine Rippletide guardrails with Blaxel sandboxes to enforce agent safety, prevent hallucinations, and run trusted agent code securely.
This integration combines [Rippletide](https://www.rippletide.com/) with Blaxel to ensure agent safety and trust. Rippletide's hypergraph database ensures agents respect guardrails and prevent hallucinations, while Blaxel provides secure sandboxes for agent code execution, enabling reliable and trusted agent deployments.
> **About this example:** This page describes the original Python customer-support example. For the current Rippletide connection workflow, start with the [official Quickstart](https://docs.rippletide.com/docs/quickstart). That CLI setup supports JavaScript and TypeScript agents and MCP servers. It is not a drop-in replacement for this Python example.
## Requirements
To use this example, you'll need a Rippletide API key, which you can obtain at [https://eval.rippletide.com](https://eval.rippletide.com).
## Clone the example repository
Clone the [example repository](https://github.com/blaxel-templates/template-rippletide-customer-support) and follow the instructions provided to install dependencies.
## Customize the knowledge base
Update the files in the `knowledge-base` directory with business- or scenario-specific information and workflows. The agent will use this information to respond to customer enquiries and to transition between workflow states.
## Create an agent
Configure the following environment variable in order to obtain an agent ID:
```bash theme={null}
RIPPLETIDE_API_KEY= # your Rippletide API key
```
Create an agent:
```bash theme={null}
uv run src/setup.py
```
Once your agent is created, the setup process will return an agent ID.
Create a `.env` file in the project root with both variables, or set them in your deployment environment.
```bash theme={null}
RIPPLETIDE_API_KEY= # your Rippletide API key
RIPPLETIDE_AGENT_ID= # your Rippletide agent ID
```
## Deploy the agent
[Deploy the agent](../Agents/Deploy-an-agent) on Blaxel. Once the agent is deployed, retrieve its [inference endpoint](../Agents/Query-agents).
You will need an API key to query the agent externally. Follow [this guide to create a service account in your workspace and generate an API key](../Security/Service-accounts).
Complete documentation for using Rippletide.
# xAI integration
Source: https://docs.blaxel.ai/Integrations/xAI
Set up the xAI integration on Blaxel to call xAI models through a unified endpoint with centralized access control and observability.
The xAI integration allows Blaxel users to **call xAI models using a Blaxel endpoint** in order to unify access control, credentials and observability management.
The integration must be set up by an [admin](../Security/Workspace-access-control) in the Integrations section in the [workspace settings](../Security/Workspace-access-control).
## Set up the integration
In order to use this integration, you must register an xAI access token into your Blaxel workspace settings. First, generate an [xAI API key](https://docs.x.ai/docs/tutorial#step-2-generate-an-api-key) from [your xAI team console](https://console.x.ai/team/default/api-keys). This key must have access to at least the *Chat* and *Models* endpoints.
On Blaxel, in Workspace Settings > xAI integration, create a new connection and paste this token into the “API key” section.
## Connect to an xAI model
Once you’ve set up the integration in the workspace, any workspace member can use it to reference an xAI model as an [external model API](../Models/External-model-apis).
When creating a model API, select xAI. You can search for any model from the xAI catalog.
After the model API is created, you will receive a dedicated global Blaxel endpoint to call the model. Blaxel will forward inference requests to xAI, using your xAI credentials for authentication and authorization.
Because your own credentials are used, any inference request on this endpoint will incur potential costs on your xAI account, as if you queried the model directly on xAI.
# Deploy jobs
Source: https://docs.blaxel.ai/Jobs/Deploy-a-job
Deploy batch jobs on Blaxel as serverless, autoscalable workloads using the CLI, GitHub integration, or Dockerfile-based deployments.
This guide assumes you have developed a job locally.
The [Blaxel SDK](../sdk-reference/introduction) allows you to connect to and orchestrate other resources (such as model APIs, tool servers, multi-agents) during development, and ensures telemetry, secure connections to third-party systems or private networks, smart global placement of workflows, and much more when jobs are deployed.
The Blaxel SDK requires two environment variables to authenticate:
| Variable | Description |
| - | - |
| `BL_WORKSPACE` | Your Blaxel workspace name |
| `BL_API_KEY` | Your Blaxel API key |
You can create an API key from the [Blaxel console](https://app.blaxel.ai/profile/security). Your workspace name is visible in the URL when you log in to the console (e.g. `app.blaxel.ai/{workspace}`).
Set them as environment variables or add them to a `.env` file at the root of your project:
```bash theme={null}
export BL_WORKSPACE=my-workspace
export BL_API_KEY=my-api-key
```
When developing locally, you can also **log in to your workspace with Blaxel CLI** (as shown above). This allows you to run Blaxel SDK functions that will automatically connect to your workspace without additional setup. When you deploy on Blaxel, authentication is handled automatically — no environment variables needed.
## Deploy on production
You can deploy the job in order to make the entrypoint function (by default: `index.ts` / `server.py`) **callable on a global endpoint**. When deploying to Blaxel, you get a dedicated endpoint that enforces your [deployment policies](../Model-Governance/Policies).
Run the following command to build and deploy a local job on Blaxel:
```bash theme={null}
bl deploy
```
You can alternatively use `bl push` to build and push the job container image to the Blaxel registry. `bl push` only publishes the image; it does not create a new sandbox deployment, update existing deployments, or restart running processes. This is useful for preparing images in advance.
## Run a job
Each deployed job has an HTTP endpoint to trigger a batch execution. You can find this endpoint in the Blaxel console, on the detail page for your deployed job.
HTTP endpoints are only available for deployed jobs.
You can invoke a job in multiple ways:
* using the Blaxel SDK, API, or language-native HTTP client (only for deployed jobs);
* using the Blaxel CLI;
* scheduling it to run periodically via a cron.
### **SDK**
Start batch job execution using the SDK:
```tsx TypeScript theme={null}
import { blJob } from "@blaxel/core";
const JOB_NAME = "YOUR_JOB_NAME"; // ex : my-job (not the display name)
async function execJob() {
const job = blJob(JOB_NAME);
let result = await job.run([{"name": "John"}, {"name": "Jane"}])
console.log(result);
}
execJob().catch(console.error);
```
```python Python (asynchronous) theme={null}
from blaxel.core.jobs import bl_job
import asyncio
async def exec_job():
try:
job = bl_job("YOUR_JOB_NAME");
result = await job.arun([{"name": "John"}, {"name": "Jane"}])
print(result)
except Exception as e:
print(f"Failed to execute job: {e}")
asyncio.run(exec_job())
```
```python Python (synchronous) theme={null}
from blaxel.core.jobs import bl_job
def exec_job():
try:
job = bl_job("YOUR-JOB-NAME");
result = job.run([{"name": "John"}, {"name": "Jane"}])
print(result)
except Exception as e:
print(f"Failed to execute job: {e}")
exec_job()
```
### **API**
Start batch job execution by [calling the inference API for your deployed batch job](../api-reference/inference).
Here is an example using `curl`:
```http Execute a job theme={null}
curl -X POST "https://api.blaxel.ai/$(bl workspace --current)/jobs/{your-job-name}/executions" \
-H "Content-Type: application/json" \
-H "X-Blaxel-Workspace: $(bl workspace --current)" \
-H "X-Blaxel-Authorization: Bearer $(bl token)" \
-d '{"tasks":[{"name":"John"},{"name":"Jane"}]}'
```
Here is an example using a language-native HTTP client:
```tsx TypeScript theme={null}
async function execJob() {
console.log("Executing job");
const WORKSPACE = "YOUR_WORKSPACE_NAME"; // ex : my-workspace (not the display name)
const JOB_NAME = "YOUR_JOB_NAME"; // ex : my-job (not the display name)
const response = await fetch(`https://api.blaxel.ai/${WORKSPACE}/jobs/${JOB_NAME}/executions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Blaxel-Authorization': `Bearer ${process.env.BL_API_KEY}`,
'X-Blaxel-Workspace': WORKSPACE
},
body: JSON.stringify({
tasks: [
{ "name": "John" },
{ "name": "Jane" }
]
})
});
if(response.status !== 200) {
console.error(`Failed to execute job : ${response.statusText}`);
return;
}
const result = await response.json();
console.log(result);
}
execJob().catch(console.error);
```
```python Python theme={null}
import os
import aiohttp
import asyncio
async def exec_job():
workspace = "YOUR_WORKSPACE_NAME" # ex : my-workspace (not the display name)
job_name = "YOUR_JOB_NAME" # ex : my-job (not the display name)
api_key = os.environ.get("BL_API_KEY")
try:
async with aiohttp.ClientSession() as session:
print("Executing job")
resp = await session.post(
f"https://api.blaxel.ai/{workspace}/jobs/{job_name}/executions",
json={
"tasks": [
{ "name": "John" },
{ "name": "Jane" }
]
},
headers={
"Content-Type": "application/json",
"X-Blaxel-Authorization": f"Bearer {api_key}",
"X-Blaxel-Workspace": workspace
}
)
data = await resp.text()
print(data)
except Exception as e:
print(f"Failed to execute job: {e}")
asyncio.run(exec_job())
```
### **CLI**
For deployed jobs, you can start a batch job execution by running:
```bash theme={null}
# Run a deployed job using Blaxel CLI with --data argument
bl run job <> --data '{"tasks": [{"name": "John"}, {"name": "Jane"}]}'
# Run a deployed job using Blaxel CLI with --file argument
bl run job <> --file batches/sample-batch.json
```
When running a job locally, only JSON file input is currently supported, and you must include the `--local` argument:
```bash theme={null}
# Run a job locally using Blaxel CLI with --local and --file argument
bl run job <> --local --file batches/sample-batch.json
```
You can cancel a job execution from the Blaxel Console or via API.
### **Cron schedule**
Set a job on a cron schedule by adding the following to the `blaxel.toml` configuration file (read full reference on *blaxel.toml* down below):
```toml theme={null}
[[triggers]]
id = "cron-trigger"
type = "cron"
[triggers.configuration]
schedule = "0 * * * *"
```
### Retries
You can set a maximum number of **retries per task** in the job definition. Check out the reference for `blaxel.toml` configuration file down below.
### Metrics
The **Blaxel Console** > **Dashboard** displays detailed job metrics, including the number of active jobs, failures, and requests (both count and requests per second (RPS)) across all deployments.
## List jobs
To retrieve all jobs in your workspace, use the Management API list endpoint with pagination.
Pagination is recommended for list operations because retrieving all available resources in a single request is slow and resource-intensive, and therefore does not scale well.
Request the first page:
```bash theme={null}
curl 'https://api.blaxel.ai/v0/jobs?limit=50' \
-H 'X-Blaxel-Authorization: Bearer YOUR-API-KEY' \
-H 'Blaxel-Version: 2026-04-28'
```
The response includes a `data` array and a `meta` object:
```json theme={null}
{
"data": [ ... ],
"meta": {
"hasMore": true,
"nextCursor": "...",
"total": 47
}
}
```
Pass `meta.nextCursor` as `cursor` on the next request and repeat until `meta.hasMore` is `false`:
```bash theme={null}
curl 'https://api.blaxel.ai/v0/jobs?limit=50&cursor=NEXT_CURSOR' \
-H 'X-Blaxel-Authorization: Bearer YOUR-API-KEY' \
-H 'Blaxel-Version: 2026-04-28'
```
For accounts with multiple workspaces, specify the workspace as an additional request parameter, such as `?workspace=WORKSPACE_NAME&...`, or via an additional `X-Blaxel-Workspace: WORKSPACE_NAME` header in the request.
For more information, refer to the [API reference documentation](/api-reference/introduction#pagination).
## Deploy with a Dockerfile
While Blaxel uses predefined, optimized container images to build and deploy your code, you can also deploy your workload using your own [Dockerfile](https://docs.docker.com/reference/dockerfile/).
Deploy resources using a custom Dockerfile.
## Deploy multiple jobs at once
Using a custom Dockerfile allows for [deploying multiple jobs from the same repository](/Jobs/Deploy-multiple-jobs) with shared dependencies.
Deploy multiple jobs with shared context from a single repository.
Reuse a job image across multiple job definitions.
## Template directory reference
### Overview
```text theme={null}
package.json # Mandatory. This file is the standard package.json file, it defines the entrypoint of the project and dependencies.
blaxel.toml # This file lists configurations dedicated to Blaxel to customize the deployment.
tsconfig.json # This file is the standard tsconfig.json file, only needed if you use TypeScript.
src/
└── index.ts # This file is the standard entrypoint of the project. It is where the core logic of the job is implemented.
└── steps # This is an example of organization for your sub steps, feel free to change it.
```
### package.json
Here the most notable imports are the scripts. They are used for the `bl serve` and `bl deploy` commands.
```json theme={null}
{
"name": "job-ts",
"version": "1.0.0",
"description": "Job using Blaxel Platform",
"main": "src/index.ts",
"keywords": [],
"license": "MIT",
"author": "Blaxel",
"scripts": {
"start": "tsx src/index.ts",
"prod": "node dist/index.js",
"build": "tsc"
},
"dependencies": {
"@blaxel/core": "0.2.5",
"@blaxel/telemetry": "0.2.5"
},
"devDependencies": {
"@types/express": "^5.0.1",
"@types/node": "^22.13.11",
"tsx": "^4.19.3",
"typescript": "^5.8.2"
}
}
```
Depending on what you do, all of the `scripts` are not required. With TypeScript, all 4 of them are used.
* `start` : start the job locally through the TypeScript command, to avoid having to build the project when developing.
* `prod` : start the job remotely from the dist folder, the project needs to have been built before.
* `build` : build the project. It is done automatically when deploying.
The remaining fields in package.json follow standard JavaScript/TypeScript project conventions. Feel free to add any dependencies you need, but keep in mind that devDependencies are only used during the build process and are removed afterwards.
### blaxel.toml
The deployment can be configured via the `blaxel.toml` file in your agent directory. This file is not mandatory; if the file is not found or a required option is not set, you will be prompted for the information during deployment.
```toml theme={null}
type = "job"
name = "my-job"
workspace = "my-workspace"
policies = ["na"]
[env]
DEFAULT_CITY = "San Francisco"
[runtime]
memory = 1024
maxConcurrentTasks = 10
timeout = 900
maxRetries=0
[[triggers]]
id = "cron-trigger"
type = "cron"
[triggers.configuration]
schedule = "0 * * * *"
tasks = [
{ p_id = "1234", arg2 = "foo" },
{ p_id = "5678", arg2 = "bar" }
]
```
* `name`, `workspace`, and `type` fields are optional and serve as default values. Any bl command run in the folder will use these defaults rather than prompting you for input.
* `policies` field is also optional. It allows you to specify a Blaxel [policy](../Model-Governance/Policies) to customize the deployment. For example, deploy it only in a specific region of the world.
* `[env]` section defines environment variables that the job can access via the SDK. Note that these are NOT secrets.
* `[runtime]` section lets you override job execution parameters: maximum number of concurrent tasks, maximum number of retries for each task, timeout (in s), or memory (in MB) to allocate.
* `[tasks]` sections let you specify
* `[[triggers]]` and `[triggers.configuration]` sections define ways to schedule job executions.
* `type = "cron"` lets you create a cron schedule for the job to run periodically. The job will be executed without any argument by default. You can override this by passing:
* `tasks = [{my_arg="my_value"}]` containing a list of tasks to execute at each occurrence of the cron (default value is one task, without arguments)
A private HTTP endpoint to launch job executions is always available by default, even if you don't define a trigger in this file. You can launch a job execution by sending a POST request with the correct job and workspace arguments to `https://api.blaxel.ai/v0/jobs/my-job/executions?workspace=my-workspace`.
## Ephemeral volumes
Ephemeral volumes are temporary disk-backed storage attached to a job at startup and automatically destroyed when the job completes. Ephemeral volumes have separate quotas from persistent volumes. Limits depend on your plan.
Ephemeral volumes are only available for jobs. Sandboxes and agents should use persistent volumes instead.
# Deploy jobs with a Dockerfile
Source: https://docs.blaxel.ai/Jobs/Deploy-dockerfile-jobs
Ship a Blaxel batch job using your own custom Dockerfile to control the runtime environment, system dependencies, and the entire build process.
Blaxel allows you to customize your deployments ([agents](../Agents/Overview), [MCP servers](../Functions/Overview), and [batch jobs](Overview)) using a Dockerfile at the root level of your project.
## Overview
By default, Blaxel builds and deploys your application using predefined container images optimized for agent workloads. However, you may need to:
* Install additional system dependencies
* Configure custom environment settings
* Use specific versions of runtime environments
* Include proprietary libraries or tools
A Dockerfile at the root of your project gives you full control over the container image that will run your workload on Blaxel's infrastructure.
1. Navigate to the root directory of your Blaxel project ([agent](../Agents/Overview), [MCP server](../Functions/Overview), or [batch job](Overview))
2. Create a file named `Dockerfile` (case-sensitive)
## Dockerfile Structure
Your Dockerfile should follow these guidelines for compatibility with Blaxel's infrastructure:
```Dockerfile Python theme={null}
# Start from a base Python image
FROM python:3.12-slim
# Set working directory
WORKDIR /blaxel
# Install system dependencies (if needed)
RUN apt-get update && apt-get install -y \\
build-essential \\
# Add any other system dependencies here \\
&& rm -rf /var/lib/apt/lists/*
# Copy requirements first for better caching
COPY pyproject.toml uv.lock /blaxel/
RUN pip install uv && uv sync --refresh
# Copy application code
COPY . .
# Set env variable to use the virtual environment
ENV PATH="/blaxel/python/.venv/bin:$PATH"
# Command to run when container starts, it needs to provide a server running on port 80 for agent and MCP server
ENTRYPOINT [".venv/bin/python3", "-m", "src"]
```
```Dockerfile TypeScript/JavaScript theme={null}
# Start from a Node.js base image
FROM node:22-alpine
# Set working directory
WORKDIR /blaxel
# Copy package files for better caching
COPY package.json pnpm-lock.yaml /blaxel/
RUN npx pnpm install
# Copy application code
COPY . .
# Command to run when container starts, it needs to provide a server running on port 80 for agent and MCP server
ENTRYPOINT ["npx", "pnpm", "start"]
```
### Entrypoint
The entrypoint must run a function that terminates - if it runs infinitely, your job will continue until it hits the execution timeout.
### Environment variables
[Environment variables](../Agents/Variables-and-secrets) configured in the Blaxel platform will be automatically injected into your container at runtime. You do not need to specify them in your Dockerfile.
## Test locally
Before deploying to Blaxel, you can test your Dockerfile locally.
```bash theme={null}
# Build the Docker image
docker build -t my-blaxel-app .
# Run the container locally
docker run -p 1338:1338 my-blaxel-app
```
## Deploy
When a Dockerfile is present at the root of your project, Blaxel will use it to build a custom container image for your deployment. Deploy your application with the Blaxel CLI as usual.
```bash theme={null}
bl deploy
```
## Deploy multiple resources with shared files
Using a custom Dockerfile allows for [deploying multiple jobs from the same repository](/Jobs/Deploy-multiple-jobs) with shared dependencies.
Deploy multiple jobs with shared context from a single repository.
# Deploy multiple jobs from a mono-repo
Source: https://docs.blaxel.ai/Jobs/Deploy-multiple-jobs
Deploy multiple Blaxel batch jobs from a single mono-repo with shared configuration, dependencies, and context to streamline complex workflows.
You can use a **shared context from a single repository** to deploy multiple resources, mixing [batch jobs](Overview), [agents](../Agents/Overview), [MCP servers](../Functions/Overview), etc.
## Deploying multiple resources
With the `--directory` (`-d`) parameter in `bl deploy`, you can specify a subfolder containing your `blaxel.toml` and `Dockerfile`.
The `Dockerfile` defines how your deployment context is built and as such is required if you want to ensure proper mounting of shared dependencies between your different services.
This enables such mono-repo structure with shared libraries:
```text theme={null}
myrepo
|- myjob
|- src
|- blaxel.toml
|- Dockerfile
|- myagent
|- src
|- blaxel.toml
|- Dockerfile
|- myotheragent
|- src
|- blaxel.toml
|- Dockerfile
|- mymcpserver
|- src
|- blaxel.toml
|- Dockerfile
|- shared
|- sharedfile
```
No changes are required to your `blaxel.toml`. However, in your `Dockerfile`, paths **must be relative** to the root context. For example, replace `COPY src src` with `COPY myagent/src src`.
This allows you to reference shared resources:
```dockerfile theme={null}
COPY myagent/src src
COPY shared shared
```
### Deploy
To deploy, run these commands from the root folder:
```bash theme={null}
bl deploy -d myjob
bl deploy -d myagent
bl deploy -d myotheragent
bl deploy -d mymcpserver
```
For a complete example, see our [sample repository](https://github.com/drappier-charles/multiagent).
# Deploy multiple jobs from a single image
Source: https://docs.blaxel.ai/Jobs/Deploy-shared-image
Build a Blaxel job image once and reuse it across multiple job definitions with different memory, disk, or region profiles, with no rebuilds.
You can build a job image once and reuse it across multiple job definitions (e.g. different memory / disk / region profiles) without rebuilding.
The workflow is:
1. Build and push the image through a single "dev" job.
2. Reference the resulting image digest from as many `Job` manifests as you need.
3. Roll out changes to production runners by re-applying the manifests.
This is particularly useful when images are large: a full rollout across every host can take up to 20 minutes, so sharing one image across many jobs is much cheaper than rebuilding per job.
## 1. Build the image via a dev job
Create a `blaxel.toml` describing the dev job used to build and host the image:
```toml theme={null}
type = "job"
name = "github-runner-dev"
[runtime]
diskPercent = 50
memory = 4096
timeout = 3600
maxRetries = 0
```
Deploy it with:
```bash theme={null}
bl deploy
```
Alternatively, use the command below to push the image without deploying it:
```bash theme={null}
bl push
```
Once deployed, obtain the image reference:
```bash theme={null}
bl get job github-runner-dev -o json | jq -r '.[].spec.runtime.image'
# -> job/github-runner-dev:jv7lsvdihfqz
```
## 2. Create one manifest per runner profile
For every runner variant you need (size, region, timeout, ...), write a small `Job` manifest that points at the image produced above.
```yaml theme={null}
# 4grunner.yaml
apiVersion: blaxel.ai/v1alpha1
kind: Job
metadata:
name: github-runner-4g
spec:
region: us-was-1
runtime:
diskPercent: 50
image: job/github-runner-dev: # from: bl get job github-runner-dev -o json | jq -r '.[].spec.runtime.image'
memory: 4096
timeout: 3600
```
Apply it:
```bash theme={null}
bl apply -f 4grunner.yaml
```
You can bootstrap a new manifest from the dev job's current spec:
```bash theme={null}
bl get job github-runner-dev -o yaml
```
## 3. Create additional jobs from the shared image
You can now use the same image to deploy jobs with different manifests/runner profiles, depending on your requirements. Because the image is shared, hosts only pull it once instead of once per job.
To update the image, you can build a new image on `github-runner-dev`, validate it there, then promote it to production by bumping the `image:` field in each manifest and re-applying.
# Develop a job
Source: https://docs.blaxel.ai/Jobs/Develop-a-job
Initialize, develop, and locally test batch processing jobs using the Blaxel CLI and SDK in TypeScript or Python before deploying them.
Jobs allow you to run many AI tasks in parallel using batch processing. Read the [introduction for a lexicon on jobs, tasks, and executions](Overview).
## Quickstart
It is required to have *npm* (TypeScript) or *uv* (Python) installed to use the following command.
You can quickly **initialize a new job from scratch** by using the CLI command `bl new job`.
```bash theme={null}
bl new job
```
This will create a pre-scaffolded local directory where your entire code can be added. In the generated folder, you'll find a boilerplate job with multiple steps in the entrypoint file `src/index.ts` / `src/main.py`. You can update this boilerplate job to your specific requirements, and manage and control job execution using the Blaxel [TypeScript](/Jobs/Manage-job-execution-ts) or [Python](/Jobs/Manage-job-execution-py) SDKs.
The template used to generate the boilerplate job is available in Blaxel's public GitHub repository ([TypeScript](https://github.com/blaxel-templates/template-jobs-ts) / [Python](https://github.com/blaxel-templates/template-jobs-py)).
Start the job locally:
```bash theme={null}
# Run the job with a sample batch file
bl run job <> --local --file batches/sample-batch.json
# Or directly with --data argument
bl run job <> --local --data '{"tasks": [{"name": "John"}]}'
# Or without blaxel CLI
pnpm start --name John
```
See a sample implementation of a job using Blaxel's TypeScript SDK
See a sample implementation of a job using Blaxel's Python SDK
Learn how to deploy your AI batch jobs on Blaxel as a serverless auto-scalable endpoint.
# Manage job execution in Python
Source: https://docs.blaxel.ai/Jobs/Manage-job-execution-py
Use the Blaxel Python SDK to start, monitor, retry, and cancel batch job executions, with examples for handling input data and execution state.
You can programmatically execute and manage batch jobs using the Blaxel API and Python SDK.
Both synchronous and asynchronous usage is supported: use `method_name` for synchronous operations and `amethod_name()` for asynchronous operations.
The code examples in this section assume that your batch job has already been successfully deployed on Blaxel.
### Dispatch job execution
Dispatch a job and wait for execution to start:
```python theme={null}
from blaxel.core.jobs import bl_job
from blaxel.core.client.models.create_job_execution_request import CreateJobExecutionRequest
job = bl_job("my-job")
request = CreateJobExecutionRequest(tasks=[{"duration": 60}])
execution_id = job.create_execution(request)
result = job.wait_for_execution(execution_id, max_wait=180)
```
Time units in the Python SDK are specified in seconds.
### Dispatch job execution with tasks
Dispatch a new job with tasks:
```python theme={null}
# synchronous
request = CreateJobExecutionRequest(tasks=[{"duration": 30}, {"duration": 60}])
execution_id = job.create_execution(request)
# asynchronous
execution_id = await job.acreate_execution(request)
```
### Get execution details
Get full execution details:
```python theme={null}
# synchronous
execution = job.get_execution(execution_id)
# asynchronous
execution = await job.aget_execution(execution_id)
```
### Get status
The possible job states are:
```text theme={null}
pending → running → completed
↘ failed
```
Get only the job status (faster than retrieving full execution details):
```python theme={null}
# synchronous
status = job.get_execution_status(execution_id)
# asynchronous
status = await job.aget_execution_status(execution_id)
```
### List executions
List all executions for a job:
```python theme={null}
# synchronous
executions = job.list_executions(limit=50)
# asynchronous
executions = await job.alist_executions(limit=50)
```
### Wait for completion
Poll until execution completes:
```python theme={null}
# synchronous
result = job.wait_for_execution(
execution_id,
max_wait=300, # 5 minutes (seconds)
interval=2 # Poll every 2 seconds (seconds)
)
# asynchronous
result = await job.await_for_execution(execution_id, max_wait=300, interval=2)
```
When polling:
* adjust the `interval` based on the expected task duration
* set the `max_wait` timeout longer than the expected total task duration
### Override environment and memory for a single job execution
Override memory and/or environment variables for a single job execution, without modifying the deployed job configuration:
```python theme={null}
from blaxel.core.jobs import bl_job
from blaxel.core.client.models.create_job_execution_request import (
CreateJobExecutionRequest,
)
from blaxel.core.client.models.create_job_execution_request_env import (
CreateJobExecutionRequestEnv,
)
job = bl_job("my-job")
env = CreateJobExecutionRequestEnv()
env["CUSTOM_ENV"] = "OVERRIDE"
env["LOG_LEVEL"] = "debug"
request = CreateJobExecutionRequest(
tasks=[{"name": "Combined"}],
memory=1024, # MB (must be <= deployed job memory)
env=env,
)
# synchronous
execution_id = job.create_execution(request)
execution = job.get_execution(execution_id)
# asynchronous
execution_id = await job.acreate_execution(request)
execution = await job.aget_execution(execution_id)
print(execution.status)
```
* Memory overrides are expressed in MB and are downward-only: the value must be \<= the memory configured at deployment time. For example, if the job is deployed with 4096 MB, execution overrides must be \<= 4096 MB.
* `env` supports multiple environment variables
* `memory` and `env` can be set independently or together
* Overrides apply only to the current execution
### Reject immediately when capacity is unavailable
By default, when your workspace is at its capacity limit, a new execution is accepted, queued, and retried in the background until capacity frees up. Set `allow_queue=False` to instead check capacity synchronously at creation time and get an immediate error if the execution cannot start right now:
```python theme={null}
request = CreateJobExecutionRequest(
tasks=[{}],
allow_queue=False,
)
# synchronous
execution_id = job.create_execution(request)
# asynchronous
execution_id = await job.acreate_execution(request)
```
* `allow_queue` omitted or `True`: the execution is queued and retried in the background (default behavior)
* `allow_queue=False`: the request returns a 429 error if capacity is unavailable right now, or a 400 error if the request can never fit within the workspace quota
* On rejection, no execution is created and nothing is queued, so there is nothing to poll or clean up
### Complete example
Here is a complete example using synchronous operations:
```python theme={null}
from blaxel.core.jobs import bl_job
from blaxel.core.client.models.create_job_execution_request import CreateJobExecutionRequest
job = bl_job("my-job")
# create execution
request = CreateJobExecutionRequest(tasks=[{"name": "John"}, {"name": "Jane"}])
execution_id = job.create_execution(request)
# monitor
status = job.get_execution_status(execution_id)
print(f"Status: {status}")
# wait
try:
result = job.wait_for_execution(execution_id, max_wait=180, interval=5)
print(f"Completed: {result.status}")
except Exception as error:
print(f"Timeout: {error}")
```
Here is a complete example using asynchronous operations:
```python theme={null}
import asyncio
from blaxel.core.jobs import bl_job
from blaxel.core.client.models.create_job_execution_request import CreateJobExecutionRequest
async def main():
job = bl_job("my-job")
request = CreateJobExecutionRequest(tasks=[{"name": "John"}, {"name": "Jane"}])
execution_id = await job.acreate_execution(request)
status = await job.aget_execution_status(execution_id)
print(f"Status: {status}")
result = await job.await_for_execution(execution_id, max_wait=180, interval=5)
print(f"Completed: {result.status}")
asyncio.run(main())
```
Learn how to deploy your AI batch jobs on Blaxel as a serverless auto-scalable endpoint.
# Manage job execution in TypeScript
Source: https://docs.blaxel.ai/Jobs/Manage-job-execution-ts
Use the Blaxel TypeScript SDK to start, monitor, retry, and cancel batch job executions, with examples for input data and execution state.
You can programmatically execute and manage batch jobs using the Blaxel API and TypeScript SDK.
The code examples in this section assume that your batch job has already been successfully deployed on Blaxel.
### Dispatch job execution
Dispatch a job and wait for execution to start:
```typescript theme={null}
import { blJob } from "@blaxel/core";
const job = blJob("my-job");
const executionId = await job.createExecution({
tasks: [{ duration: 60 }]
});
const result = await job.waitForExecution(executionId);
```
Time units in the TypeScript SDK are specified in milliseconds.
### Dispatch job execution with tasks
Dispatch a new job with tasks:
```typescript theme={null}
const executionId = await job.createExecution({
tasks: [
{ duration: 30 },
{ duration: 60 },
]
});
```
### Get execution details
Get full execution details:
```typescript theme={null}
const execution = await job.getExecution(executionId);
console.log(execution.status, execution.metadata);
```
### Get status
The possible job states are:
```text theme={null}
pending → running → completed
↘ failed
```
Get only the job status (faster than retrieving full execution details):
```typescript theme={null}
const status = await job.getExecutionStatus(executionId);
// Returns: "pending" | "running" | "completed" | "failed"
```
### List executions
List all executions for a job:
```typescript theme={null}
const executions = await job.listExecutions();
console.log(`Found ${executions.length} executions`);
```
### Wait for completion
Poll until execution completes:
```typescript theme={null}
const result = await job.waitForExecution(executionId, {
maxWait: 300000, // 5 minutes (milliseconds)
interval: 2000, // Poll every 2 seconds (milliseconds)
});
```
When polling:
* adjust the `interval` based on the expected task duration
* set the `maxWait` timeout longer than the expected total task duration
### Override environment and memory for a single job execution
Override memory and/or environment variables for a single job execution, without modifying the deployed job configuration:
```typescript theme={null}
import { blJob } from "@blaxel/core"
const job = blJob("my-job")
const executionId = await job.createExecution({
tasks: [{ name: "Combined" }],
memory: 1024, // MB (must be <= deployed job memory)
env: {
TEST_MODE: "true",
LOG_LEVEL: "debug",
},
})
const execution = await job.getExecution(executionId)
console.log(execution.status)
```
* Memory overrides are expressed in MB and are downward-only: the value must be \<= the memory configured at deployment time. For example, if the job is deployed with 4096 MB, execution overrides must be \<= 4096 MB.
* `env` supports multiple environment variables
* `memory` and `env` can be set independently or together
* Overrides apply only to the current execution
### Reject immediately when capacity is unavailable
By default, when your workspace is at its capacity limit, a new execution is accepted, queued, and retried in the background until capacity frees up. Set `allowQueue: false` to instead check capacity synchronously at creation time and get an immediate error if the execution cannot start right now:
```typescript theme={null}
const executionId = await job.createExecution({
tasks: [{}],
allowQueue: false,
});
```
* `allowQueue` omitted or `true`: the execution is queued and retried in the background (default behavior)
* `allowQueue: false`: the request returns a 429 error if capacity is unavailable right now, or a 400 error if the request can never fit within the workspace quota
* On rejection, no execution is created and nothing is queued, so there is nothing to poll or clean up
### Complete example
Here is a complete example:
```typescript theme={null}
import { blJob } from "@blaxel/core";
const job = blJob("data-processing");
// create execution
const executionId = await job.createExecution({
tasks: [{"name": "John"}, {"name": "Jane"}]
});
// monitor
let status = await job.getExecutionStatus(executionId);
console.log(`Status: ${status}`);
// wait
try {
const result = await job.waitForExecution(executionId, {
maxWait: 180000,
interval: 5000,
});
console.log(`Completed: ${result.status}`);
} catch (error) {
console.log(`Timeout: ${error.message}`);
}
```
Learn how to deploy your AI batch jobs on Blaxel as a serverless auto-scalable endpoint.
# Jobs
Source: https://docs.blaxel.ai/Jobs/Overview
Run batch processing tasks for AI workflows on Blaxel as scheduled or on-demand jobs that scale automatically and run on serverless infrastructure.
Jobs allow you to run many AI tasks in parallel using batch processing.
## Concepts
* **Job**: A code definition that specifies a batch processing task. Jobs can run multiple times within a single execution and accept optional input parameters.
* **Execution**: A specific instance of running a batch job at a given timestamp. Each execution consists of multiple tasks running in parallel.
* **Task**: A single instance of a job definition running as part of an execution.
## Get started with Batch Jobs
Read our guide for developing batch AI jobs leveraging Blaxel developer tools.
Learn how to deploy your batch job on Blaxel as a serverless endpoint.
Learn how to manage secrets and environment variables for your batch job.
# Variables and secrets for jobs
Source: https://docs.blaxel.ai/Jobs/Variables-and-secrets-jobs
Configure environment variables and secrets for Blaxel batch jobs to securely manage API keys, credentials, and runtime configuration values.
Environment variables are retrieved first from your `.env` file (or any other file you specify when deploying, see down below), and if not found there, from the `[env]` section of `blaxel.toml`. This fallback mechanism allows for two kinds of variables:
* secrets
* simple environment variables
## Secrets
Create a file named `.env` at the root of your project to store secrets. Blaxel will retrieve them from this default location upon deployment (see down below to override). Add the `.env` file to your `.gitignore` to prevent committing sensitive variables.
```bash theme={null}
MY_SECRET=123456
```
You can then use secrets in your code as follows:
```typescript TypeScript theme={null}
import { env } from "@blaxel/core";
console.info(env.MY_SECRET); // 123456
```
```python Python theme={null}
import os
os.environ.get('MY_SECRET')
```
### Use secrets from another env file
To separate local *.env* from production, specify a different environment file when deploying your resource to Blaxel using the `--env-file` argument in Blaxel CLI:
```bash theme={null}
bl deploy --env-file anyenvfile
```
This lets you specify a different environment file for production secrets instead of relying on the default *.env*, e.g.:
* Use `.env` for local development
* Use `.env.prod` for production deployment
## Variables
You can define variables inside your agent or MCP server in the `blaxel.toml` file at root level of your project. These variables are NOT intended to be used as secrets, but as configuration variables.
```toml blaxel.toml {6} theme={null}
name = "..."
workspace = "..."
type = "function"
[env]
DEFAULT_CITY = "San Francisco"
```
You can then use it in your code as follows:
```typescript TypeScript theme={null}
import { env } from "@blaxel/core";
console.info(env.DEFAULT_CITY); // San Francisco
```
```python Python theme={null}
import os
os.environ.get('DEFAULT_CITY')
```
## Build variables
Build variables let you pass secrets and configuration values into the Docker build phase without exposing them at runtime. This is useful when your build process needs credentials that should never appear inside the deployed job.
Two options exist: `.env.build` for build secrets and `blaxel.toml` file for non-secret build variables.
### .env.build
Create a `.env.build` file in the root of your project for build secrets. A common example of this is installing private npm packages, which require an `NPM_TOKEN` during `npm install`. Variables defined here are injected during the build phase only and are never persisted in the runtime environment.
```bash .env.build theme={null}
MY_SECRET_BUILD_VAR=I_AM_A_SECRET
```
Use the `--build-env-file` argument to `bl deploy` to specify a custom file name or path instead of the default `.env.build`.
Ensure that `.env.build` is ignored during commits to avoid accidentally making secrets public.
### blaxel.toml
Non-secret build variables can be placed in the `blaxel.toml` file, as in the example below:
```toml blaxel.toml theme={null}
type = "job"
name = "my-job"
[build.args]
MY_BUILD_VAR = "I_AM_NOT_A_SECRET"
```
## Reserved variables
The following variables are reserved by Blaxel:
`PORT` : Port of the HTTP server, it needs to be set to allow Blaxel platform to configure it
`HOST` : Host of the HTTP server, it needs to be set to allow Blaxel platform to configure it
Internal URL for Blaxel platform, to avoid linking multiple instances through the Internet
`BL_AGENT_${envVar}_SERVICE_NAME`
`BL_FUNCTION_${envVar}_SERVICE_NAME`
`BL_RUN_INTERNAL_HOSTNAME`: internal run url
Override URL to link multiple agents and MCP servers together locally
`BL_AGENT_${envVar}_URL`
`BL_FUNCTION_${envVar}_URL`
Metadata automatically set by Blaxel platform in production
`BL_WORKSPACE` : workspace name
`BL_NAME` : name of the function or the agent
`BL_TYPE` : function or agent
Authentication environment variables
`BL_CLIENT_CREDENTIALS` : client credentials, used by Blaxel in production with a workspace service account
`BL_API_KEY` : can be set in your code to connect with the platform (locally or from a server not on Blaxel platform)
`BL_LOG_LEVEL` : Log level, default to info, can be set to debug,warn,error
`BL_DEBUG_TELEMETRY`: Enable telemetry debug mode, will print each interaction with OpenTelemetry
`BL_ENABLE_OPENTELEMETRY`: Enable OpenTelemetry, it's set automatically by the platform in production
# Policies
Source: https://docs.blaxel.ai/Model-Governance/Policies
Define policies that control where workloads run on the Global Agentics Network, including location constraints, flavors, and token limits.
Policies are used to program how and where your workloads are deployed on Blaxel. Policies can be defined as code, allowing for easy programming and customization of your [Global Agentics Network](../Infrastructure/Global-Inference-Network).
Policies apply to the entities they are attached to: [model APIs](../Models/Overview), [functions](../Functions/Overview) and [agent](../Agents/Overview) deployments.
## Policies overview
Policies essentially describe rules as to how deployments and executions are made on Blaxel.
A policy states all the **allowed options for a specific aspect** (called the *policy type*) of the deployment or execution (for example: the execution location).
Example:
* Policy `Country: US` means that attached workloads will only be able to run in locations that are in the United States.
When no policies are enforced on a type, all options for this type are considered allowed. Workloads are executed using [Global Agentics Network](../Infrastructure/Global-Inference-Network)’s default optimizations.
### Policy types
Policies have a **type**, allowing multiple policies to drive various deployment strategies without colliding. Typically, you can easily enforce a policy on the execution location and a policy on the underlying hardware at the same time.
There are currently three types of policies: **location,** **flavor,** and **token usage**.
### Location policies
Location policies give control over which clusters will execute your workloads.
They come in two different formats:
* policies on **countries** let you define all [physical locations](/Model-Governance/Policies) inside of one or several countries at once
* for example, execute only in the following country: *USA*
* policies on **continents** let you define all [physical locations](/Model-Governance/Policies) inside of one or several continents at once
* for example, execute only in *North America*
### Flavor policies
Flavor policies give control over which underlying hardware your workloads will be executed on.
They come in two different formats:
* policies on **cpu** let you pass a specific list of CPU types
* for example, execute only on x86
### Token usage policies
Token usage policies control the maximum number of tokens your [**model APIs**](../Models/Overview) can handle within a specific time period. You can control the maximum number of input tokens, output tokens, **and/or** total tokens. When a model reaches its maximum token limit, subsequent requests are rejected with a 429 error.
The policy only drops complete requests AFTER the maximum limit is reached. The first request that exceeds the threshold will still be processed. However, all subsequent requests within the enforced time period will be dropped.
## Create a policy
Policies can be created from the Blaxel console, or from the Blaxel APIs and CLI.
Read [our complete reference on policies](https://docs.blaxel.ai/api-reference/policies/create-or-update-policy).
## Attach a policy
Attaching a policy to a workload enforces it on the workload.
When no policies are enforced on a type, all options for this type are considered allowed. Workloads are executed using [Global Agentics Network](../Infrastructure/Global-Inference-Network)’s default optimizations.
### Attaching multiple policies
When attaching **multiple policies** to a resource, it's crucial to understand their combined effect.
**If you are attaching multiple policies to the same resource:**
Their combined effect is the **UNION** of all of their effects for the same [type](/Model-Governance/Policies) of policy (a.k.a. *OR* clause), and **INTERSECTION** across all [types](/Model-Governance/Policies) of policies (a.k.a. *AND* clause).
For example:
* Let’s assume the following policies:
* Policy A: Country is: USA
* Policy B: Continent is: North America, or Europe
* if a workload has the following combined policies:
* A and B: then the workload will only execute in any location in either North America (including USA) or Europe — on any kind of hardware available there.
## Policy reference
Below is the list of official names to build policies.
### Flavors
**type**: `flavor`
| **Code** | **Type** | **Flavor Name** |
| - | - | - |
| CPU x86 | cpu | x86 |
### Locations
**type**: `location`
| **Code** | **Type** | **Name** |
| - | - | - |
| eu | continent | Europe |
| na | continent | North America |
| us | country | United States |
### Token usage
**type**: `maxToken`
* `granularity`: the unit period of time over which the number of tokens is evaluated. One of: `month`, `day`, `hour`, `minute`
* `step`: the number of time period units over which the number of tokens is evaluated. It is a number greater than 1.
* `input`: threshold for the maximum number of **input** tokens. If 0, this metric is not evaluated.
* `output`: threshold for the maximum number of **output** tokens. If 0, this metric is not evaluated.
* `total`: threshold for the maximum number of **input and output** tokens. If 0, this metric is not evaluated.
# External model APIs
Source: https://docs.blaxel.ai/Models/External-model-apis
Control and secure agent access to AI models from OpenAI, Anthropic, Mistral, and other top providers behind the Blaxel Global Inference Network.
You can query any LLM or other generative AI model from top API providers via Blaxel, in order to benefit from a **unified layer of access control and telemetry,** especially when running whole AI agents.
## Creating a model endpoint on Blaxel
Adding model endpoints from external providers on Blaxel gives you single endpoints to call the model on the same base URL, rather than calling each provider separately. Blaxel handles the authentication, authorization and monitoring automatically for you.
This is done in a two-step process:
1. First, you need to create a workspace integration that will contain the credentials to connect to your model API provider. Check out the dedicated documentation for each available integration.
2. Second, you can create a dedicated endpoint for a specific model from this provider. You will need to choose the model from the list of available models for the provider.
You can then query the model using its [dedicated global inference endpoint](/Models/Query-a-model). Any call to this endpoint will be passed to the underlying provider.
## Provider reference
The following providers are available:
* [OpenAI](../Integrations/OpenAI)
* [Anthropic](../Integrations/Anthropic)
* [Mistral AI](../Integrations/MistralAI)
* [Cohere](../Integrations/Cohere)
* [xAI](../Integrations/xAI)
* [DeepSeek](../Integrations/DeepSeek)
# Model APIs
Source: https://docs.blaxel.ai/Models/Overview
Connect external model API providers or deploy custom models on Blaxel for unified gateway access with centralized credentials and observability.
AI models are the brain of AI agents, as they are able to reason, talk, and generate payloads for the tools that the agent can use.
There are two ways to approach models on Blaxel:
* **Using an external model API provider** (e.g. OpenAI, Together, etc.): Blaxel acts as a unified gateway for model APIs, centralizing access credentials, tracing and telemetry. You can achieve this by defining workspace integrations to any major model API provider, and creating gateway endpoints on Blaxel for any of their models.
* **Bringing your own model**: Deploy a custom model on Blaxel, allowing you to use fine-tuned SLMs/LLMs or any other kind of AI model. When a model is deployed on Blaxel, you get a global API endpoint to call it. This option is part of our Enterprise offering, contact us at [support@blaxel.ai](mailto:support@blaxel.ai) for more information.
Complete guide for connecting to an external model provider like Anthropic or OpenAI.
# Query a model API
Source: https://docs.blaxel.ai/Models/Query-a-model
Make inference requests to model APIs via their global endpoints on the Global Agentics Network, with ChatCompletions and custom sub-endpoint support.
Model APIs on Blaxel have a unique **inference endpoint** which can be used by external consumers and agents to request an inference execution. Inference requests are then routed on the [Global Agentics Network](../Infrastructure/Global-Inference-Network) based on the [deployment policies](../Model-Governance/Policies) associated with your model API.
## Inference endpoints
Whenever you deploy a model API on Blaxel, an **inference endpoint** is generated on Global Agentics Network.
The inference URL looks like this:
```http Query model API theme={null}
POST https://run.blaxel.ai/{YOUR-WORKSPACE}/models/{YOUR-MODEL}
```
### Specific API endpoints in your model
The URL above calls your model and can be called directly. However, your model may **implement additional endpoints.** These sub-endpoints will be hosted on this URL.
For example, if you are calling a text generation model that also implements the ChatCompletions API:
* calling `run.blaxel.ai/your-workspace/models/your-model` (the base endpoint) will generate text based on a prompt
* calling `run.blaxel.ai/your-workspace/models/your-model/v1/chat/completions` (the ChatCompletions API implementation) will generate a response based on a list of messages
### Endpoint authentication
It is necessary to authenticate all inference requests, via a [bearer token](../Security/Access-tokens). The evaluation of authentication/authorization for inference requests is managed by the Global Agentics Network based on the [access given in your workspace](../Security/Workspace-access-control).
Making a workload publicly available is not yet available. Please contact us at [support@blaxel.ai](mailto:support@blaxel.ai) if this is something that you need today.
## Make an inference request
### Blaxel API
Make a **POST** request to the [inference endpoint](/Models/Query-a-model) for the model API you are requesting, making sure to fill in the [authentication token](/Models/Query-a-model):
```bash theme={null}
curl 'https://run.blaxel.ai/YOUR-WORKSPACE/models/YOUR-MODEL' \
-H 'accept: application/json, text/plain, */*' \
-H 'x-Blaxel-authorization: Bearer YOUR-TOKEN' \
-H 'x-Blaxel-workspace: YOUR-WORKSPACE' \
--data-raw $'{"inputs":"Enter your input here."}'
```
Read about [the API parameters in the reference](https://docs.blaxel.ai/api-reference/inference).
### Blaxel CLI
The following command will make a default POST request to the model API.
```bash theme={null}
bl run model your-model --data '{"inputs":"Enter your input here."}'
```
You can call [specific API endpoints](/Models/Query-a-model) that your model implements with the option `--path`:
```bash theme={null}
bl run model your-model --path /v1/chat/completions --data '{"inputs":"Hello there!"}'
```
Read about [the CLI parameters in the reference](https://docs.blaxel.ai/cli-reference/bl_run).
### Blaxel console
Inference requests can be made from the Blaxel console from the model API’s **Playground** page.
# Logs & traces
Source: https://docs.blaxel.ai/Observability/Overview
Get automatic logging, distributed tracing, and real-time metrics including latency, token usage, and request data for agents deployed on Blaxel.
Deploying and running agents on Blaxel gives you **total observability by design**, without needing to install any additional library. When you either deploy from the console’s low-code builder or by using the Blaxel SDK to wrap your code for deployment, Blaxel will automatically implement logging & tracing on your requests.
## Monitor from the Blaxel console
There are three ways you can observe and monitor your running workloads:
* Metrics
* Logs
* Traces
### Metrics
Metrics are aggregated data about workload executions. They include:
* Number and rate of requests
* End-to-end latency: average, p50, p90, p99
* Number of tokens generated by model APIs: input, output and total
* City and country of origin of all requests
### Logs
Logs are timestamped data about what happens with your agents, functions and model APIs. Such data includes:
* Status of all requests on agents, functions and model APIs
* Logs generated when building your deployments
* Custom logs generated by your agents and functions
### Traces
Tracing helps you understand how your agent works by showing you all its parts in action. When you make a request, it creates a trace that contains multiple *spans*.
Think of a span as a building block - it shows when something starts and ends, what went in, what came out, and other useful details. Spans can contain other spans, like when one function calls another. You'll often see spans for things like LLM calls, tool calls, or steps in an agent's process. You can click on any trace to see all its spans laid out clearly. This makes it much easier to follow how your agent works and fix any problems you find.
Blaxel collects and saves the traces of a **sampled 10%** of all your executions. In order to force saving the trace on an execution, call the run API and add query parameter `debug:true`.
#### Add custom spans
You can add your own information to traces, such as inputs, outputs, and intermediate results. This is useful when you need to perform detailed debugging.
```typescript TypeScript theme={null}
import "@blaxel/telemetry";
import { startSpan } from "@blaxel/telemetry";
const span = startSpan("span-name", {
attributes: {
"input": JSON.stringify(userInput),
},
});
try {
// ... your code here ...
span.setAttribute("action.result", JSON.stringify(result));
} finally {
span.end();
}
```
```python Python theme={null}
import blaxel.telemetry
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("span-name", attributes={"input": user_input}) as span:
# ... your code here ...
span.set_attribute("action.result", str(result))
```
You can call `startSpan` anywhere inside a request handler, and the span will be attached to the active trace for that request.
## Opt out
This feature is controlled by the `BL_ENABLE_OPENTELEMETRY` environment variable.
When you deploy an agent to Blaxel, the platform automatically injects `BL_ENABLE_OPENTELEMETRY=true` into the environment.
When developing locally, this environment variable is not set and therefore defaults to `false`.
To explicitly disable telemetry:
```bash theme={null}
export BL_ENABLE_OPENTELEMETRY=false
```
Setting `DO_NOT_TRACK=1` has no effect on OpenTelemetry.
[Read more about SDK and CLI data collection and privacy](/Security/Data-collection-and-privacy).
# Blaxel: infrastructure for autonomous agents
Source: https://docs.blaxel.ai/Overview
Introduction to Blaxel core building blocks: perpetual sandboxes, Agent Drive shared filesystem, and networking primitives.
Blaxel is the **infrastructure for autonomous agents**. Our platform lets you keep infinite, secure sandboxes on automatic standby while co-hosting your agents for near instant latency.
**Using Cursor, Claude Code, Codex, Goose, or another AI coding agent?**
The [Blaxel skills](./skills-mcp) give your agent the ability to deploy AI agents, create sandboxes for code execution, host MCP servers, run batch jobs, and more...all using simple prompts, zero code required!
[Installing the Blaxel CLI](./Get-started) installs them for the coding agents on your machine. If you already have the CLI, run:
```shell theme={null}
bl skills install
```
An *AI agent* is any application that leverages generative AI models to take autonomous actions in the real world. These agents often require computing power to execute their interactions.
Some examples of agents include:
* **Conversational agents** that are able to take action in the world while keeping a human in the loop for activation or validation: for example code generation agents with real-time previews.
* **AI-powered data pipelines**: for example a data transformation pipeline that retrieves unstructured video files and uses an AI model to extract structured data then act on it.
* **RAG agents**: for example, a chatbot assistant that can better answer consumers’ queries by autonomously running scripts to access relevant databases.
* **Autonomous system agents** that handle machine-to-machine workflows: like a smart traffic monitoring system that analyzes video feeds, detects accidents in real-time, and automatically dispatches emergency services with AI-generated incident reports.
Blaxel’s infrastructure platform gives production-grade agents their own computing environments including code sandboxes, tool servers, and LLMs. It offers infrastructure to run these agents on a global network that makes them run fast and reliably.
This website provides comprehensive documentation and API, SDK and CLI references to help you work with Blaxel.
## Essential concepts
Blaxel is the infrastructure for autonomous agents. It **doesn't force you into any kind of workflow** or shaped box. While we encourage you to exploit architecture designs that we consider are more reliable, our toolkit gives you all the pieces you need to build reliable agentic systems exactly the way you want.
Blaxel consists of modular services that are engineered to work seamlessly together, but you can also just use any one of them independently. Think of it as a purpose-built set of building blocks that you can use to power agents.
### The building blocks
At the heart of Blaxel is our flagship **perpetual sandbox** service. Sandboxes are secure, instant-launching compute environments that you can use for running AI code. Blaxel lets you keep sandboxes on automatic standby with optimized agent hosting for near instant latency.
With Blaxel sandboxes, you get:
* Automatic scale-to-zero after 5s inactivity, resume from standby under 25ms even after weeks.
* microVMs with full access to file system, processes and logs — and native support for Zero Data Retention (ZDR).
* Preview URLs with your own custom domain.
You can co-locate agent APIs, MCP servers, and batch tasks directly alongside your sandboxes to eliminate network hops and ensure the lowest possible end-to-end latency. For this, Blaxel also offers:
* **Agents Hosting** - Deploy your AI agents as serverless auto-scalable endpoints. Completely framework agnostic: just bring your code, Blaxel builds it and runs it for you.
* **Batch Jobs** - Scalable compute engine designed for agents to schedule and execute many AI processing tasks in parallel in the background.
* **MCP Servers Hosting** - Deploy custom tool servers on a fast-starting infrastructure to extend your agents' capabilities.
* **Model Gateway** - Intelligent routing layer to LLM providers with built-in telemetry, token cost control, and fallback capabilities.
* **Full observability** - out-of-the-box.
### A cloud built for agents
Agents will transform how we work in the coming years. Traditional cloud providers weren't designed to handle them and their one-size-fits-all architecture holds them back. We built Blaxel to fix that.
Blaxel is a cloud where **AI agents themselves are the primary users**. All products are accessible through [MCP servers](skills-mcp), allowing agents to create and manage resources via tool calls. Blaxel provides agents with all the compute they need to scale and perform optimally: products like Sandboxes give them their own dedicated personal computer(s) / computing environments, while Batch Jobs enable them to schedule background tasks at scale.
Building effectively on this cloud means embracing a few operational practices unique to how Blaxel works:
* Blaxel sandboxes automatically scale up and down at near-instant speeds, unlike traditional sandbox providers. Let sandboxes suspend automatically when idle instead of deleting them. Resuming from standby is near-instant, even after long periods away. Learn more about the [sandbox lifecycle](/Sandboxes/best-practices#use-the-sandbox-lifecycle-strategically), including session length and the standby-storage cost tradeoff in detail.
* For guaranteed long-term data persistence beyond a sandbox's own lifecycle, use [Volumes](/Volumes/Overview) or [Agent Drive](/Agent-drive/Overview). If you persist data this way, you can delete the sandbox itself; to resume, re-create the sandbox (\~2–4 seconds) and restart processes to restore state.
* [TTLs and lifecycle policies](/Sandboxes/best-practices#automate-cleanup-with-ttls) let you automatically clean up sandboxes you no longer need.
* When you delete a sandbox, all data is immediately erased. If the sandbox was never put into standby mode, Blaxel guarantees ZDR (zero data retention).
* Break down and distribute your agents whenever possible. A single monolithic agent handling all tool calls, LLM calls, and task workflows can be deployed to Blaxel Agents Hosting - but it will be harder to maintain, monitor, and will use resources inefficiently. Blaxel SDKs allow builders to split services and connect them from your code.
* Similarly, while direct tool calls are possible, deploying separate MCP servers improves reusability, optimizes resources, and simplifies monitoring. Blaxel also optimizes placement globally when your serverless tool server needs to make multiple backend calls.
### Which component should I use?
When building your agentic system, you'll need to make architecture design choices. Blaxel offers several high-performance compute options, summarized below in order of latency performance, alongside two storage products (Volumes and Agent Drive) for long-term persistence:
* [**Sandboxes**](Sandboxes/Overview): Perfect for maximum workload flexibility. These microVMs provide full access to filesystem, network, and processes, booting from standby in under 25ms.
* [**Agents Hosting (sync mode)**](https://docs.blaxel.ai/Agents/Query-agents#default-synchronous-endpoint): Ideal for running HTTP API services that process requests within a few seconds.
* [**Agents Hosting (async mode)**](https://docs.blaxel.ai/Agents/Query-agents#async-endpoint): Best for running HTTP API services handling longer requests without maintaining an open connection.
* [**Batch Jobs**](Jobs/Overview): Designed for asynchronous tasks that may run for extended periods where boot latency is less critical. These jobs are triggered by providing specific input parameters, unlike Agents that are a fully hosted API.
| **Product** | **Typical use** | **Typical workload duration** | **Boot time** | **Input type** |
| - | - | - | - | - |
| Sandboxes | Giving an agent its own compute runtime | seconds to hours | \~25ms (from standby) | Fully custom |
| Agents Hosting (sync mode) | Agent API that answers fast | a few seconds (**maximum 100 s**) | \~25ms | Custom API endpoints |
| Agents Hosting (async mode) | Agent API that processes data for a while | a few minutes (**maximum 10 mins**) | \~25ms | Custom API endpoints |
| Batch Jobs | Sub-tasks scheduled in an agentic workflow | minutes to hours (**maximum 24 h**) | \~30s | Specific input parameters |
| MCP Servers Hosting | Running an MCP server API | seconds to minutes (**maximum 10 mins**) | \~25ms | API following MCP |
| Volumes | Long-term persistent block storage attached to a sandbox or agent | Persists independently of any sandbox/agent lifecycle | N/A (attaches near-instantly) | Block storage (POSIX) |
| Agent Drive | Shared distributed filesystem across sandboxes and agents | Persists independently of any sandbox/agent lifecycle | N/A (attaches near-instantly) | Distributed filesystem (POSIX + S3) |
Volumes and Agent Drive are storage products, so "boot time" and "workload duration" don't apply.
### Which storage solution to choose
Choose storage based on durability and sharing needs:
* [**Sandbox filesystem**](/Sandboxes/Overview#memory-and-filesystem): Fastest option (in-memory, no network or disk I/O). Use it for scratch files, working directories, and outputs that only need to live for the current sandbox. It survives standby and resume, but is erased when the sandbox is deleted. Not replicated or durable; capacity is roughly half the sandbox memory allocation.
* [**Volumes**](/Volumes/Overview): Durable block storage that survives sandbox deletion and attaches to one sandbox at a time. Use it for high-performance persistence in a single sandbox lineage (databases, source checkouts, agent memories). Mounts only at sandbox creation; re-attaching to a new sandbox costs a cold boot of a few hundred ms.
* [**Agent Drive**](/Agent-drive/Overview): Shared distributed filesystem that multiple sandboxes or agents can mount and write concurrently. Use it to share state across sandboxes, sessions, or tenants. Attach or detach on running sandboxes at any path, with fine-grained permissions. Prefer a Volume for single-writer block workloads like databases.
As a rule of thumb: if the data only needs to survive the current sandbox session, use the sandbox filesystem. If it needs to outlive the sandbox but only one sandbox will ever touch it at a time, use a Volume. If more than one sandbox or agent needs to read or write it at the same time, use Agent Drive. Because the sandbox filesystem is not replicated or durable, if the data is not easily recreated, use a Volume or Agent Drive instead of relying on it alone.
For a deeper comparison, see [Choose the right storage for your Blaxel agents](https://blaxel.ai/blog/choose-the-right-storage-for-your-blaxel-agents).
## The Blaxel powerhouse
Blaxel is a fully managed cloud platform. For regulated or enterprise customers who require "self-hosted" options: private network connectivity and bring-your-own-server arrangements are available; [contact us](https://blaxel.ai/contact) to discuss your requirements.
When you deploy workloads to Blaxel, they run on a technical backbone called the **Global Agentics Network**. Its natively serverless architecture automatically scales computing resources without any server management on your part.
Global Agentics Network serves as the powerhouse for the entire Blaxel platform, from Agents Hosting to Sandboxes. It is natively **distributed** in order to optimize for low-latency or other strategies. It allows for multi-region deployment, enabling AI workloads (such as an AI agent processing inference requests) to run across multiple geographic areas or cloud providers. This is accomplished by decoupling this execution layer from a data layer made of a smart distributed network that federates all those execution locations.
Finally, the platform implements advanced security measures, including fine-grained authentication and authorization through Blaxel IAM, ensuring that your AI infrastructure remains protected. It can be interacted with through various methods, including APIs, CLI, web console, and MCP servers.
## Documentation structure
You might want to start with any of the following articles:
* [**Get started**](Get-started): Deploy your first workload on Blaxel in just 3 minutes.
* [**Agent Skill & MCP:**](skills-mcp) Let your coding agent (Cursor, Claude Code, Windsurf…) create and manage Blaxel resources using natural language.
* **Product Documentation**
* [**Sandboxes**](Sandboxes/Overview): Equip your agents with fast & secure virtual machines to run AI code.
* [**Agents Hosting**](Agents/Overview): Host and run AI agents as serverless auto-scalable endpoints.
* [**Batch Jobs**](Jobs/Overview): Background tasks for your AI workflows that run in batches.
* [**MCP Servers Hosting**](Functions/Overview): Expose capabilities and execute tool calls using MCP.
* [**Model APIs**](Models/Overview): Learn about supported model types on our global AI gateway.
* [**Agent Drive**](Agent-drive/Overview): Share files across sandboxes and agents with a distributed filesystem.
* [**Volumes**](Volumes/Overview): Persist files long-term by attaching volumes to your resources.
* [**Integrations**](Integrations): Discover how Blaxel works with other tools, frameworks, and platforms.
* [**Observability**](Observability/Overview): Monitor logs, traces and metrics for your agent runs.
* [**Governance**](Model-Governance/Policies): Manage your AI deployment strategies with policies and environments.
* [**Security**](Security/Workspace-access-control): Implement robust security measures for your AI infrastructure.
* [**Regions**](Infrastructure/Regions): Discover where Blaxel is available in the world.
* [**Troubleshooting**](troubleshooting): Resolve common issues with Blaxel deployments.
* **Tutorials & Examples**
* [**Sandbox tutorials**](Tutorials/Sandboxes-Overview): Run web applications in sandboxes with Astro, Expo, and Next.js.
* [**Agent tutorials**](Tutorials/Agents-Overview): Deploy agents using popular frameworks like Claude Agent SDK, LangChain, CrewAI, and more.
* **References**
* [**API reference**](api-reference/introduction): Comprehensive guide to Blaxel's APIs.
* [**SDK reference**](sdk-reference/introduction): Manage Blaxel resources programmatically using TypeScript, Python, or Go SDKs.
* [**CLI reference**](cli-reference/introduction): Learn how to use Blaxel's command-line interface.
* [**Deployment reference**](deployment-reference): Complete reference for the blaxel.toml configuration file.
# Archive and unarchive sandboxes
Source: https://docs.blaxel.ai/Sandboxes/Archive
Archive a sandbox to keep its filesystem while shutting it down, and unarchive it later to get it back.
This feature is currently in private preview and is not recommended for production use. It requires a feature flag on your workspace: [contact us](https://blaxel.ai/contact) or email [support@blaxel.ai](mailto:support@blaxel.ai) to get access. Until then, the archive and unarchive actions are hidden in the Blaxel console, and both API operations answer `403 Forbidden`.
Archiving a sandbox stores the filesystem changes it made over its image, then shuts the sandbox down: nothing runs and no memory is kept, and you stop paying for it. The sandbox keeps its name, its configuration, and its preview URLs, so unarchiving gives you the same sandbox back with its files.
Archiving is not a snapshot: memory is not preserved. All processes that were running are restarted automatically from their configuration, with new IDs, when the sandbox is unarchived.
## Archive a sandbox
The export runs in the background. By default the SDK waits until the sandbox reaches the `ARCHIVED` status.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
await sandbox.archive();
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
await sandbox.archive()
```
```bash HTTP API theme={null}
curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/archive \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace"
```
Archive a sandbox by name, without retrieving it first:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
await SandboxInstance.archive("my-sandbox");
```
```python Python theme={null}
from blaxel.core import SandboxInstance
await SandboxInstance.archive("my-sandbox")
```
## Unarchive a sandbox
Unarchiving starts the sandbox again from its image and writes the archived filesystem back over it. All processes that were running are restarted automatically. The sandbox and its terminal answer while the restore runs, and the SDK waits until the sandbox is `DEPLOYED` again.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
await sandbox.unarchive();
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
await sandbox.unarchive()
```
```bash HTTP API theme={null}
curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/unarchive \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace"
```
## Control the wait
An archive and its restore take longer as the filesystem grows. Pass `wait: false` in TypeScript, or `wait=False` in Python, to return as soon as the operation is launched, then read the sandbox status yourself. Set `maxWait`/`max_wait` and `interval`, both in milliseconds, to change how long the SDK waits and how often it reads the sandbox.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
await sandbox.archive({ wait: false });
await sandbox.unarchive({ maxWait: 600_000, interval: 5_000 });
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
await sandbox.archive(wait=False)
await sandbox.unarchive(max_wait=600_000, interval=5_000)
```
## Statuses
| Status | Meaning |
| - | - |
| `ARCHIVING` | The filesystem is being exported and the sandbox is about to be shut down |
| `ARCHIVED` | The filesystem is stored and the sandbox is shut down |
| `UNARCHIVING` | The sandbox is running again and its archived filesystem is being restored |
| `DEPLOYED` | The sandbox is running, and the restore is complete |
A sandbox keeps a single archive. Archiving a sandbox again replaces the previous archive, and deleting a sandbox deletes its archive, whatever its status.
Learn more about sandbox lifecycle and configuration.
Checkpoint a sandbox, memory included, and fork it into a new one.
# Handle CORS on Blaxel
Source: https://docs.blaxel.ai/Sandboxes/CORS
Configure CORS headers on Blaxel preview URLs so browser frontends on another origin can call your sandbox applications, including with credentials.
When a browser frontend on one origin calls a Blaxel [preview URL](./Preview-url), the browser enforces CORS. This page explains which CORS headers Blaxel returns, how to change them, and how to check the result.
## How Blaxel handles CORS
Blaxel handles CORS differently for preflight requests and normal responses on a preview URL.
* Preflight (`OPTIONS`) requests are answered by Blaxel directly. They never reach your application, and they do not require authentication, even on a private preview.
* Normal responses come from your application. Blaxel adds CORS headers only when your application does not set them.
By default, a preflight response contains:
```text theme={null}
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS, PATCH
Access-Control-Allow-Headers: Content-Type, Authorization, X-Blaxel-Preview-Token
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
Access-Control-Expose-Headers: X-Blaxel-Source, X-Blaxel-Error-Code, X-Blaxel-Dispatch-State, Retry-After
```
These defaults work for requests without credentials that only send the listed headers. You need to configure the preview when:
* your frontend sends other request headers
* your frontend sends credentials (`fetch(..., { credentials: "include" })`), because browsers reject `Access-Control-Allow-Origin: *` on credentialed requests
## Configure CORS headers on a preview
Set CORS headers in the preview's `responseHeaders`. Blaxel applies them in the following order:
| Response | Header source, from highest to lowest priority |
| - | - |
| Preflight (`OPTIONS`) | `responseHeaders`, then Blaxel defaults |
| Normal response | Your application, then `responseHeaders`, then Blaxel defaults |
When you set CORS headers:
* Set `Access-Control-Allow-Origin` to your frontend's exact origin (scheme, host and port, without a trailing slash).
* List every request header your frontend sends in `Access-Control-Allow-Headers`. Your value replaces the default list, so add `X-Blaxel-Preview-Token` back if your frontend sends it.
* Omit `Access-Control-Allow-Credentials`, because Blaxel already adds `Access-Control-Allow-Credentials: true`.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
const preview = await sandbox.previews.create({
metadata: { name: "app" },
spec: {
port: 3000,
public: false,
responseHeaders: {
"Access-Control-Allow-Origin": "https://app.example.com",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Your-Header",
},
},
});
console.log(preview.spec.url);
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
preview = await sandbox.previews.create({
"metadata": {"name": "app"},
"spec": {
"port": 3000,
"public": False,
"responseHeaders": {
"Access-Control-Allow-Origin": "https://app.example.com",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Your-Header",
},
},
})
print(preview.spec.url)
```
```bash theme={null}
# To update an existing preview, send a `PUT` request with the full spec: `port`, `public` and `responseHeaders`. Changes apply within a few seconds.
curl -X PUT "https://api.blaxel.ai/v0/sandboxes/my-sandbox/previews/app" \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: $BL_WORKSPACE" \
-H "Content-Type: application/json" \
-d '{
"metadata": {"name": "app"},
"spec": {
"port": 3000,
"public": false,
"responseHeaders": {
"Access-Control-Allow-Origin": "https://app.example.com",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Your-Header"
}
}
}'
```
## Handle CORS in your application
Your application can set its own CORS headers on normal responses, for example to echo the request `Origin` from a list of allowed origins. Blaxel keeps the values set by your application.
Your application cannot handle preflights, because Blaxel answers them. Set the preflight headers in the preview's `responseHeaders`.
## Send credentials to a private preview
Browsers send credentials to a private preview when you call it with `credentials: "include"`. Open `/?bl_preview_token=` once in the browser: Blaxel sets a `bl_preview_token` cookie (`SameSite=None; Secure`) that the browser sends with later credentialed requests. You can also send the token in the `X-Blaxel-Preview-Token` header. For details on tokens, see [private preview URLs](./Preview-url#private-preview-urls).
For credentialed requests, set `Access-Control-Allow-Origin` to your frontend's origin, as described in [Configure CORS headers on a preview](#configure-cors-headers-on-a-preview).
When a request to a private preview fails authentication, Blaxel returns a `401` with `Access-Control-Allow-Origin: *`. On a credentialed request, the browser reports a CORS error instead of the `401`. If the preflight passes but the request fails with a CORS error, check your token first.
## Check your CORS configuration
Send a preflight with curl:
```bash theme={null}
PREVIEW_URL="https://.preview.bl.run" # from preview.spec.url
ORIGIN="https://app.example.com"
curl -si -X OPTIONS "$PREVIEW_URL/api/anything" \
-H "Origin: $ORIGIN" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: content-type,x-your-header" \
| grep -i '^access-control'
```
The output contains your origin and your header list. If you see `access-control-allow-origin: *`, the preview does not have your headers. Check `preview.spec.responseHeaders`.
To check a normal response, send a regular request. For a private preview, pass a preview token:
```bash theme={null}
curl -si "$PREVIEW_URL/api/anything" \
-H "Origin: $ORIGIN" \
-H "X-Blaxel-Preview-Token: $PREVIEW_TOKEN" \
| grep -i '^access-control-allow-origin'
```
To check in the browser, open your frontend at `$ORIGIN` and run the following in the devtools console:
```javascript theme={null}
await fetch("/api/anything", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json", "X-Your-Header": "1" },
body: "{}",
}).then(r => r.status);
```
The call returns a status code, with no CORS error. In the Network tab, the `OPTIONS` request shows your origin in `access-control-allow-origin`.
## Limitations
* One origin per preview. Preflight headers are static, so a preview allows a single origin for credentialed requests. To allow several origins (for example staging and production), create one preview per origin.
* Static header list. Every request header your frontend sends must be listed in `Access-Control-Allow-Headers`.
* Preflight cache. Browsers cache preflights for up to 24 hours (`Access-Control-Max-Age: 86400`). After changing the headers, hard-reload the page or test in a new private window.
Create public and private preview URLs for a sandbox.
Serve a preview on your own domain.
# Code generation tools
Source: https://docs.blaxel.ai/Sandboxes/Codegen-tools
Use Blaxel sandbox tools and functions optimized for AI code generation, including file editing, formatting, search, and execution helpers.
Blaxel Sandboxes provide tools for managing files and their contents, specialized for code generation ("*codegen*") use cases. These tools are accessible through the sandboxes' [MCP server](https://docs.blaxel.ai/Sandboxes/Overview#mcp-server-for-a-sandbox) and [REST API](https://docs.blaxel.ai/api-reference/root/welcome-message).
## Fast apply of file edits
Fast apply of file edits is powered by [Morph](https://morphllm.com/) or [Relace](https://www.relace.ai/) and requires you to bring your own Morph/Relace account.
With this tool, you can **apply code changes** suggested by an LLM to your existing code files fast (2000+ tokens/second).
Traditional code generation requires generating the entire file every time, which can be slower for large files. With this approach your LLM only generates the specific changes needed, and this tool applies them to the original file.
### Configure environment variables
Pass your [Morph API key](https://docs.morphllm.com/api-reference/introduction#authentication) and Morph model (default = *morph-v2*) set as environment variables when creating the sandbox.
```tsx TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
// Create sandbox with Morph API key for fast code edits
const sandbox = await SandboxInstance.createIfNotExists({
name: "codegen-sandbox",
image: "blaxel/nextjs:latest",
memory: 4096,
region: "us-pdx-1",
ports: [
{ name: "preview", target: 3000 }
],
envs: [
{ name: "MORPH_API_KEY", value: process.env.MORPH_API_KEY || "" },
{ name: "MORPH_MODEL", value: process.env.MORPH_MODEL || "morph-v2" }
]
});
```
```python Python theme={null}
import os
from blaxel.core import SandboxInstance
# Create sandbox with Morph/Relace API key for fast code edits
sandbox = await SandboxInstance.create_if_not_exists({
"name": "codegen-sandbox",
"image": "blaxel/nextjs:latest",
"memory": 4096,
"region": "us-pdx-1",
"ports": [
{ "name": "preview", "target": 3000 }
],
"envs": [
{ "name": "MORPH_API_KEY", "value": os.getenv("MORPH_API_KEY") },
{ "name": "MORPH_MODEL", "value": os.getenv("MORPH_MODEL") or "morph-v2" }
]
})
```
Pass your [Relace API key](https://docs.relace.ai/api-reference/introduction#authentication) as an environment variable when creating the sandbox.
```tsx TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
// Create sandbox with Relace API key for fast code edits
const sandbox = await SandboxInstance.createIfNotExists({
name: "codegen-sandbox",
image: "blaxel/nextjs:latest",
memory: 4096,
region: "us-pdx-1",
ports: [
{ name: "preview", target: 3000 }
],
envs: [
{ name: "RELACE_API_KEY", value: process.env.RELACE_API_KEY || "" }
]
});
```
```python Python theme={null}
import os
from blaxel.core import SandboxInstance
# Create sandbox with Relace API key for fast code edits
sandbox = await SandboxInstance.create_if_not_exists({
"name": "codegen-sandbox",
"image": "blaxel/nextjs:latest",
"memory": 4096,
"region": "us-pdx-1",
"ports": [
{ "name": "preview", "target": 3000 }
],
"envs": [
{ "name": "RELACE_API_KEY", "value": os.getenv("RELACE_API_KEY") },
]
})
```
### Use the tool via Blaxel SDK
Call the `fastapply` endpoint of the [Sandbox API](https://docs.blaxel.ai/api-reference/fastapply/apply-code-edit) via the Blaxel SDK to fast-apply a targeted edit to a specified file with Morph or Relace, with instructions and partial contents.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
// Apply code changes
await sandbox.codegen.fastapply(
"/tmp/test.ts",
"// keep existing code // add a typescript function to calculate the area of a circle"
);
// Read file with changes
let content = await sandbox.fs.read("/tmp/test.ts");
console.log(content)
```
```python Python theme={null}
from blaxel.core import SandboxInstance
logger = logging.getLogger(__name__)
# Apply code changes
await sandbox.codegen.fastapply(
"/tmp/test.ts",
"// keep existing code // add a typescript function to calculate the area of a circle;"
)
# Read file with changes
content = await sandbox.fs.read("/tmp/test.ts")
logger.info(content)
```
### Use the tool via MCP
Call the `codegenEditFile` tool on the **[MCP server of a sandbox](https://docs.blaxel.ai/Sandboxes/Overview#mcp-server-for-a-sandbox)** to fast-apply a targeted edit to a specified file, with instructions and partial contents.
Use Blaxel SDK to retrieve the tool in any [compatible agent framework](../Tutorials/Agents-Overview) (here in AI SDK format):
```typescript TypeScript theme={null}
import { blTools } from '@blaxel/vercel';
// Get codegen tools from sandbox MCP
const allTools = await blTools([`sandboxes/${sandbox.metadata.name}`]);
// Filter for specific codegen tools
const codegenTools = Object.fromEntries(
Object.entries(allTools).filter(([key]) =>
key.startsWith('codegen')
)
);
```
```python Python theme={null}
from blaxel.langgraph import bl_tools
# Get codegen tools from sandbox MCP
all_tools = await bl_tools([f"sandboxes/{sandbox.metadata.name}"])
# Filter for specific codegen tools
codegen_tools = [tool for tool in all_tools if tool.name.startswith("codegen")]
```
## Other tools built for codegen
Use the following codegen-optimized functions by making tool calls through the MCP server or REST API of a sandbox. See example above on how to retrieve and execute the tools.
| Tool | Description |
| - | - |
| `codegenCodebaseSearch` | Semantic search to find relevant code snippets |
| `codegenFileSearch` | Fast fuzzy file path search |
| `codegenGrepSearch` | Exact regex search using ripgrep engine |
| `codegenListDir` | List directory contents (quick discovery) |
| `codegenReadFileRange` | Read file contents within a specific line range (max 250 lines) |
| `codegenRerank` | Performs semantic search/reranking on code files in a directory |
| `codegenParallelApply` | Plan parallel edits across multiple file locations |
| `codegenReapply` | Use smarter model to retry a failed edit |
Build a Claude Agent SDK agent that connects to a Blaxel sandbox and operates it using the sandbox's MCP server.
# Expiration policies
Source: https://docs.blaxel.ai/Sandboxes/Expiration
Configure expiration policies to automatically delete Blaxel sandboxes after inactivity, a TTL, or other conditions to control snapshot storage costs.
While Blaxel is designed for you to keep your sandboxes in standby perpetually, it also supports automatic deletion of sandboxes based on specific expiration policies. This is useful if you want to avoid keeping snapshot storage for a long tail of sandboxes that will never be reactivated again.
Blaxel has a [quota tiering system](https://app.blaxel.ai/account/quotas) that unlocks higher limits and features on the platform as your tier progresses. Some quota tiers enforce expiration dates (Tier 0 and 1, with respectively up to 7 and 30 days). In higher tiers, unlimited persistence is permitted.
Automatic deletion differs from the automatic standby (*scale-to-zero*) which happens to **all sandboxes** when inactive and where the memory and filesystem are snapshotted to be resumed instantly.
## Expiration policies
The following options are available for sandbox expiry and deletion:
* expire after a total maximum lifetime (time-to-live or TTL) from creation using the `ttl` parameter
* expire at a specific date using the `expires` parameter
* expire based on a combination of one or more lifecycle policies, specified with the `lifecycle.expirationPolicies` / `lifecycle.expiration_policies` parameter
* `ttl-max-age`: Delete after total lifetime from creation (equivalent to `ttl`) (1h, 24h, 7d)
* `ttl-idle`: Delete after period of inactivity from last active time (1h, 24h, 7d)
* `date`: Delete at a specific date (equivalent to `expires`)
If these parameters are absent, sandboxes will not be deleted.
For `ttl-idle`, "activity" is counted as either a **resume** or a **suspend** — not anything in between. If a sandbox hasn't been in standby since it was last woken up, the idle timer starts from the time of that first wake-up. This means that if your session runs continuously for longer than the idle TTL without going into standby, the sandbox will be deleted once the TTL elapses, even if it is actively in use.
The sandbox metadata includes a read-only parameter, `expiresIn`, that computes the number of seconds until a sandbox is terminated due to its TTL or lifecycle policy.
## Configure sandbox expiry rules at creation
You can define a sandbox's time-to-live (TTL) configuration at creation time:
```typescript TypeScript highlight={11-23} theme={null}
import { SandboxInstance } from "@blaxel/core";
// Create a new sandbox
const sandbox = await SandboxInstance.create({
name: "my-sandbox",
image: "blaxel/base-image:latest",
memory: 4096,
ports: [{ target: 3000, protocol: "HTTP" }],
region: "us-pdx-1",
ttl: "24h", // Total duration before auto-deletion. Supported units: h, d, w
// OR
// expires: new Date(Date.now() + 86400000) // Alternative: set a date at which it will be deleted
// OR / AND
lifecycle: {
expirationPolicies: [
{
type: "ttl-idle",
value: "24h", // Delete after 24 hours of inactivity. Supported units: h, d, w
action: "delete"
}
]
}
});
```
```python Python highlight={11-23} theme={null}
from blaxel.core import SandboxInstance, SandboxLifecycle, ExpirationPolicy
# Create a new sandbox
sandbox = await SandboxInstance.create({
"name": "my-sandbox",
"image": "blaxel/base-image:latest",
"memory": 4096,
"ports": [{ "target": 3000 }],
"region": "us-pdx-1",
"ttl": "24h", ## Total duration before auto-deletion. Supported units: h, d, w
## OR
## expires: new Date(Date.now() + 86400000) // Alternative: set a date at which it will be deleted
## OR / AND
"lifecycle": SandboxLifecycle(
expiration_policies=[
ExpirationPolicy(
type_="ttl-idle",
value="24h", # Delete after 24 hours of inactivity. Supported units: h, d, w
action="delete"
)
]
)
})
```
Important notes:
* Three lifecycle expiration policies are supported: `ttl-idle`, `ttl-max-age` and `date`. Check out the [API reference](/api-reference/compute/update-sandbox) for full documentation.
* The `ttl`, `ttl-idle` and `ttl-max-age` parameters accept a string with the following time units: `h` (hours), `m` (minutes), `d` (days), and `w` (weeks).
* You can combine multiple expiration policies. Whichever condition is met first will trigger the deletion.
## Update sandbox expiry rules
Updating a sandbox's TTL or expiration policies applies to the running sandbox: it is not restarted and keeps its filesystem, forks included.
You can update a sandbox's time-to-live (TTL) configuration after it has been created:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
// Update sandbox TTL
const sandbox = await SandboxInstance.updateTtl("my-sandbox", "30m");
```
```python Python theme={null}
from blaxel.core import SandboxInstance
# Update sandbox TTL
sandbox = await SandboxInstance.update_ttl("my-sandbox", "30m")
```
You can also update the expiration policy of a sandbox after it has been created:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
// Update sandbox expiration policy
await SandboxInstance.updateLifecycle("my-sandbox", {
expirationPolicies: [
{ type: "ttl-max-age", value: "30m", action: "delete" }
]
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
# Update sandbox expiration policy
await SandboxInstance.update_lifecycle(
"my-sandbox",
{
"expiration_policies": [
{ "type": "ttl-max-age", "value": "30m", "action": "delete" }
]
},
)
```
## Clear sandbox expiry rules
To make a sandbox persist again, clear its TTL or lifecycle policies. Clearing does not recreate the sandbox or affect its filesystem.
Pass `null` (or an empty string) to `updateTtl` to remove the TTL:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
// Clear the sandbox TTL
const sandbox = await SandboxInstance.updateTtl("my-sandbox", null);
```
```python Python theme={null}
from blaxel.core import SandboxInstance
# Clear the sandbox TTL
sandbox = await SandboxInstance.update_ttl("my-sandbox", None)
```
Pass `null` to `updateLifecycle` to remove all expiration policies:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
// Remove all expiration policies
await SandboxInstance.updateLifecycle("my-sandbox", null);
```
```python Python theme={null}
from blaxel.core import SandboxInstance
# Remove all expiration policies
await SandboxInstance.update_lifecycle("my-sandbox", None)
```
# File system
Source: https://docs.blaxel.ai/Sandboxes/Filesystem
Read, write, list, and delete files inside a Blaxel sandbox using a simple file system interface from the SDK, MCP server, or REST API.
Manage files and directories within sandboxes through the `fs` module of Blaxel SDK. This module provides essential operations for creating, reading, writing, copying, and deleting files and directories.
Refer to our [blog post on choosing the right storage for your Blaxel agents](https://blaxel.ai/blog/choose-the-right-storage-for-your-blaxel-agents) for more information.
Complete code examples demonstrating all operations are available on Blaxel's GitHub: [in TypeScript](https://github.com/blaxel-ai/sdk-typescript/tree/main/tests/sandbox), [in Python](https://github.com/blaxel-ai/sdk-python/tree/main/tests/integration/sandbox), and [in Go](https://github.com/blaxel-ai/sdk-go/tree/main/integration_tests).
## Basic file system operations
The Blaxel SDK requires two environment variables to authenticate:
| Variable | Description |
| - | - |
| `BL_WORKSPACE` | Your Blaxel workspace name |
| `BL_API_KEY` | Your Blaxel API key |
You can create an API key from the [Blaxel console](https://app.blaxel.ai/profile/security). Your workspace name is visible in the URL when you log in to the console (e.g. `app.blaxel.ai/{workspace}`).
Set them as environment variables or add them to a `.env` file at the root of your project:
```bash theme={null}
export BL_WORKSPACE=my-workspace
export BL_API_KEY=my-api-key
```
The Blaxel SDK does not accept credentials as constructor arguments. Credentials must come from environment variables, a `.env` file, or a local CLI login session (see below).
When developing locally, you can also **log in to your workspace with Blaxel CLI** (as shown above). This allows you to run Blaxel SDK functions that will automatically connect to your workspace without additional setup. When you deploy on Blaxel, authentication is handled automatically — no environment variables needed.
### Create directory
Create a new directory at a specific path in the sandbox:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
await sandbox.fs.mkdir("/blaxel/app/uploads");
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
await sandbox.fs.mkdir("/blaxel/app/uploads")
```
### List files
List files in a specific path:
```typescript TypeScript theme={null}
const { subdirectories, files } = await sandbox.fs.ls("/blaxel/app");
```
```python Python theme={null}
result = await sandbox.fs.ls("/blaxel/app")
directories = result.subdirectories
files = result.files
```
#### Address file paths over HTTP
Filesystem paths are part of the URL when you call the sandbox API directly over HTTP. The API resolves a path without a leading slash relative to the filesystem service's working directory. To address an absolute path, URL-encode the leading slash as `%2F`.
For example, if the filesystem working directory is `/app`:
| Request path | Resolved filesystem path |
| - | - |
| `/filesystem/workspace` | `/app/workspace` |
| `/filesystem/%2Fworkspace` | `/workspace` |
The CLI's double-slash form, such as `--path /filesystem//workspace`, is the CLI equivalent for an absolute path. For a direct HTTP request, use `%2F` for the leading slash:
```bash theme={null}
curl -X GET \
"https://sbx-{YOUR-SANDBOX}-{YOUR-WORKSPACE}.{REGION}.bl.run/filesystem/%2Fworkspace" \
-H "X-Blaxel-Authorization: Bearer "
```
The same rule applies when initiating a multipart upload. The following example starts an upload to the absolute path `/workspace/archive.tar`, uploads one part, and completes the upload. Repeat the part request with increasing `partNumber` values for additional parts, then include every returned `etag` in the completion request.
```bash theme={null}
# Initiate an upload to /workspace/archive.tar.
curl -X POST \
"https://sbx-{YOUR-SANDBOX}-{YOUR-WORKSPACE}.{REGION}.bl.run/filesystem-multipart/initiate/%2Fworkspace/archive.tar" \
-H "X-Blaxel-Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{"permissions":"0644"}'
# Use the uploadId returned above to upload each part.
curl -X PUT \
"https://sbx-{YOUR-SANDBOX}-{YOUR-WORKSPACE}.{REGION}.bl.run/filesystem-multipart//part?partNumber=1" \
-H "X-Blaxel-Authorization: Bearer " \
-F "file=@archive.part-1"
# Complete the upload with the etag returned for each part.
curl -X POST \
"https://sbx-{YOUR-SANDBOX}-{YOUR-WORKSPACE}.{REGION}.bl.run/filesystem-multipart//complete" \
-H "X-Blaxel-Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{"parts":[{"partNumber":1,"etag":""}]}'
```
This URL-encoding requirement applies only when the filesystem path is embedded in an HTTP URL. SDK filesystem methods accept normal paths such as `/workspace`.
### Find files and directories
Blaxel's Sandbox API uses an optimized `find()` method, which is faster than using the native `find` tool.
Find files and directories matching specified patterns:
```typescript TypeScript theme={null}
let result = await sandbox.fs.find(
"/app",
{
type: "file",
patterns: ["*.md", "*.html"],
maxResults: 1000,
}
);
if (result.matches && result.matches.length > 0) {
for (const file of result.matches) {
console.log(`Found file: ${file.path}`);
}
} else {
console.log("No files found.");
}
```
```python Python theme={null}
result = await sandbox.fs.find(
path="/app",
type="file",
patterns=["*.md", "*.html"],
max_results=1000,
)
if result.matches:
for file in result.matches:
logger.info(f"Found file: {file.path}")
else:
logger.info("No files found.")
```
### Search for text content within files
Blaxel's Sandbox API uses an optimized `grep()` method, which is faster than using the native `grep` tool.
Find files containing specified text content:
```typescript TypeScript theme={null}
let result = await sandbox.fs.grep(
"agentic",
"/app",
{
caseSensitive: true,
contextLines: 2,
maxResults: 5,
filePattern: "*.mdx",
excludeDirs: ["images", "node_modules"],
}
);
if (result.matches && result.matches.length > 0) {
for (const match of result.matches) {
console.log(`${match.path}:${match.line} -> ${match.text}`);
}
} else {
console.log("No matches found.");
}
```
```python Python theme={null}
result = await sandbox.fs.grep(
query="agentic",
path="/app",
case_sensitive=True,
context_lines=2,
max_results=5,
file_pattern="*.mdx",
exclude_dirs=["images", "node_modules"],
)
if result.matches:
for match in result.matches:
logger.info(
f"{match.path}:{match.line} -> {match.text}"
)
else:
logger.info("No matches found.")
```
### Read file
Read a file from a specific filepath:
```typescript TypeScript theme={null}
const content = await sandbox.fs.read("/blaxel/app/config.json");
```
```python Python theme={null}
content = await sandbox.fs.read("/blaxel/app/config.json")
```
### Read many files
Discover relative file paths with `fs.find`, then read text files in batches of four. This returns flat path/content pairs with one discovery call plus one read call per file, not a single bulk-read request.
`find` defaults to 20 results, so do not assume its default response includes every file. Request one more than your chosen file cap and reject the result if that extra match is returned. `excludeDirs` (`exclude_dirs` in Python) matches directory names, not path globs; apply file exclusions by filtering paths separately.
```typescript TypeScript theme={null}
const root = "/blaxel/app/schemas";
const maxFiles = 20;
const result = await sandbox.fs.find(root, {
type: "file",
patterns: ["*.json"],
excludeDirs: ["node_modules", ".git"],
maxResults: maxFiles + 1,
});
const matches = result.matches ?? [];
if (matches.length > maxFiles) {
throw new Error("File cap exceeded; narrow your search.");
}
const files: Array<{ path: string; content: string }> = [];
for (let offset = 0; offset < matches.length; offset += 4) {
const batch = await Promise.all(
matches.slice(offset, offset + 4).map(async (file) => ({
path: file.path,
content: await sandbox.fs.read(`${root}/${file.path}`),
}))
);
files.push(...batch);
}
```
```python Python theme={null}
import asyncio
root = "/blaxel/app/schemas"
max_files = 20
result = await sandbox.fs.find(
root,
type="file",
patterns=["*.json"],
exclude_dirs=["node_modules", ".git"],
max_results=max_files + 1,
)
matches = result.matches or []
if len(matches) > max_files:
raise ValueError("File cap exceeded; narrow your search.")
async def read_file(file):
return {
"path": file.path,
"content": await sandbox.fs.read(f"{root}/{file.path}"),
}
files = []
for offset in range(0, len(matches), 4):
batch = await asyncio.gather(
*(read_file(file) for file in matches[offset:offset + 4])
)
files.extend(batch)
```
### Export a whole directory as one archive
Create an archive on disk with `tar`, then download it with one binary read. This uses two calls for creation and download, plus a cleanup call. Your sandbox image must include `tar`. Keep the temporary archive outside the directory you are exporting.
```typescript TypeScript theme={null}
import { randomUUID } from "node:crypto";
const archivePath = `/tmp/schemas-${randomUUID()}.tar.gz`;
let archiveData: Blob;
try {
const result = await sandbox.process.exec({
command: `tar -czf '${archivePath}' -C '/blaxel/app/schemas' .`,
waitForCompletion: true,
timeout: 30,
});
if (result.exitCode !== 0) {
throw new Error("Archive creation failed.");
}
archiveData = await sandbox.fs.readBinary(archivePath);
} finally {
await sandbox.fs.rm(archivePath);
}
```
```python Python theme={null}
from uuid import uuid4
archive_path = f"/tmp/schemas-{uuid4()}.tar.gz"
try:
result = await sandbox.process.exec({
"command": f"tar -czf '{archive_path}' -C '/blaxel/app/schemas' .",
"wait_for_completion": True,
"timeout": 30,
})
if result.exit_code != 0:
raise RuntimeError("Archive creation failed.")
archive_data = await sandbox.fs.read_binary(archive_path)
finally:
await sandbox.fs.rm(archive_path)
```
Do not pipe base64-encoded file contents through process logs. Output can be truncated, and file contents are exposed in retained logs. Use the binary filesystem read instead.
Neither recipe guarantees a consistent snapshot if files change during the operation.
### Write file
Create a file in a specific path:
```typescript TypeScript theme={null}
await sandbox.fs.write("/blaxel/app/config.json", "{}");
```
```python Python theme={null}
await sandbox.fs.write("/blaxel/app/config.json", "{}")
```
See down below for how to upload/write a binary, or multiple files at once.
### Write multiple files
You can write multiple files or directories simultaneously. The second path parameter in `writeTree` specifies the base directory for writing the file tree, eliminating the need to repeat the full path for each file.
```typescript TypeScript theme={null}
const files = [
{ path: "src/app.js", content: "console.log('Hello');" },
{ path: "src/utils.js", content: "export const helper = () => {};" },
{ path: "package.json", content: '{"name": "my-app"}' },
{ path: "docs/README.md", content: "# My App" }
];
await sandbox.fs.writeTree(files, "/blaxel/app");
```
```python Python theme={null}
files = [
{"path": "src/app.py", "content": "print('Hello')"},
{"path": "src/utils.py", "content": "def helper(): pass"},
{"path": "requirements.txt", "content": "flask==2.0.1"},
{"path": "docs/README.md", "content": "# My App"}
]
await sandbox.fs.write_tree(files, "/blaxel/app")
```
### Read binary file
Read a binary file from the sandbox filesystem:
```typescript TypeScript theme={null}
const binaryData = await sandbox.fs.readBinary("/tmp/image.webp");
```
```python Python theme={null}
binary_data = await sandbox.fs.read_binary("/tmp/image.webp")
```
The binary content is returned as a Web API *Blob* object.
### Write binary file
Write binary content to a file in the sandbox filesystem:
```typescript TypeScript theme={null}
const binaryData = fs.readFileSync("./image.webp");
await sandbox.fs.writeBinary("/blaxel/app/assets/image.webp", binaryData);
```
```python Python theme={null}
with open("image.webp", "rb") as f:
binary_data = f.read()
await sandbox.fs.write_binary("/blaxel/app/assets/image.webp", binary_data)
```
The binary content to write can be provided as:
* *Buffer*: Node.js Buffer object
* *Blob*: Web API Blob object
* *File*: Web API File object
* *Uint8Array*: Typed array containing binary data
If you're calling the sandbox filesystem API directly over HTTP instead of using an SDK, a single request is capped at 5 MB. Requests larger than this are rejected with an HTTP 413 before they reach the sandbox. Therefore, if using HTTP, larger files must be uploaded in parts via the sandbox's multipart upload endpoints rather than in a single request. Blaxel SDKs handle this automatically: SDK functions like `write()`, `writeBinary()`, and `write_binary()` automatically split files over 5 MB and send via multipart upload in 5 MB chunks.
### Download file to host
Download a file from the sandbox filesystem to the host:
```typescript TypeScript theme={null}
await sandbox.fs.download("/tmp/foo.bin", "foo2.bin");
```
```python Python theme={null}
await sandbox.fs.download("/tmp/foo.bin", "foo2.bin")
```
### Copy file
Copy a file from a path to another path:
```typescript TypeScript theme={null}
await sandbox.fs.cp("/blaxel/app/config.json", "/blaxel/app/config.backup.json");
```
```python Python theme={null}
await sandbox.fs.cp("/blaxel/app/config.json", "/blaxel/app/config.backup.json")
```
### Delete file or directory
Delete a file or directory by specifying its path:
```typescript TypeScript theme={null}
await sandbox.fs.rm(`/blaxel/app/config.json`);
```
```python Python theme={null}
await sandbox.fs.rm(f"/blaxel/app/config.json")
```
## Watch filesystem for events
The `watch` function monitors all file system changes **in the specified directory.** You can also watch subdirectories by passing a `/my/directory/**` pattern.
By default (when *withContent: false*), the events will only include metadata about the changes, not the actual file contents. Here's what you'll get in the callback events:
1. For ALL operations (CREATE, WRITE, DELETE, etc.), you'll receive:
1. op: The operation type (e.g., "CREATE", "WRITE", "DELETE")
2. path: The directory path where the change occurred
3. name: The name of the file/directory that changed
2. You will NOT receive:
1. The actual content of the files
2. File contents for CREATE or WRITE operations
```typescript TypeScript theme={null}
// You can specify if you want the content of the files or not
const handle = sandbox.fs.watch("/", (fileEvent) => {
console.log(fileEvent.op, fileEvent.path, fileEvent.content)
}, {
withContent: true
});
// Do file operations
// At the end, close the watch handle
handle.close();
```
```python Python theme={null}
def watch_callback(file_event):
print(file_event.op, file_event.path, file_event.content)
# You can specify if you want the content of the files or not
handle = sandbox.fs.watch("/", watch_callback, { "with_content": True })
# Do file operations
# At the end, close the watch handle
handle["close"]()
```
### Watch sub-directories
Watch all sub-directories recursively with `/**`:
```typescript TypeScript theme={null}
const handle = sandbox.fs.watch("/folder/**", (fileEvent) => {
console.log(fileEvent.op, fileEvent.path)
});
```
```python Python theme={null}
def watch_callback(file_event):
print(file_event.op, file_event.path)
# You can specify if you want the content of the files or not
handle = sandbox.fs.watch("/folder/**", watch_callback)
```
### Ignore files or directories
You can ignore changes in certain files or directories by providing an array of filepaths to ignore:
```typescript TypeScript theme={null}
const handle = sandbox.fs.watch("/", (fileEvent) => {
console.log(fileEvent.op, fileEvent.path)
}, {
ignore: ["/folder", "/folder_two/test2.txt"]
});
```
```python Python theme={null}
def watch_callback(file_event):
print(file_event.op, file_event.path)
# You can specify if you want the content of the files or not
handle = sandbox.fs.watch("/", watch_callback, {
"ignore": ["/folder", "/folder_two/test2.txt"]
})
```
Specify `withContent: true` so the events include the actual file contents.
# Snapshots and forking
Source: https://docs.blaxel.ai/Sandboxes/Fork
Create point-in-time snapshots of sandboxes, roll them back to a checkpoint, and fork them into new sandboxes.
This feature is currently in private preview and is not recommended for production use.
A snapshot captures the state of a sandbox at a point in time: its filesystem, its running processes, and its memory. Use snapshots to checkpoint a sandbox before a risky change, to roll it back later, or as the basis for forking a new sandbox.
A snapshot outlives the sandbox it was captured from: it stays usable, and forkable, after that sandbox is deleted.
## Create a snapshot
Capture a snapshot from the sandbox you want to checkpoint. Snapshot names are unique within a sandbox, and creating a snapshot with a name the sandbox already uses fails with a `409` error. Every snapshot also gets an `id`, unique across your workspace, which the operations that do not go through the sandbox take instead of the name.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
const snapshot = await sandbox.snapshots.create("my-snapshot");
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
snapshot = await sandbox.snapshots.create("my-snapshot")
```
```bash HTTP API theme={null}
curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/snapshots \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace"
```
You can also create a snapshot directly through the snapshot API, by passing the name of the sandbox to snapshot as `source`:
```typescript TypeScript theme={null}
import { Snapshot } from "@blaxel/core";
const snapshot = await Snapshot.create({
name: "my-snapshot",
source: { name: "my-sandbox" },
});
```
```python Python theme={null}
from blaxel.core import Snapshot
snapshot = await Snapshot.create({
"name": "my-snapshot",
"source": {"name": "my-sandbox"},
})
```
```bash HTTP API theme={null}
curl -X POST https://api.blaxel.ai/v0/snapshots \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d '{
"name": "my-snapshot",
"source": { "name": "my-sandbox" }
}'
```
`POST /sandboxes/{name}/snapshots` generates the snapshot name for you, so it cannot collide with an existing one. Use `POST /snapshots` with a `source` to choose the name. Sandbox-side operations take the snapshot name, workspace-side operations take `snapshot.id`.
## List snapshots
List from a sandbox to get only the snapshots captured from it:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
const snapshots = await sandbox.snapshots.list();
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
snapshots = await sandbox.snapshots.list()
```
```bash HTTP API theme={null}
curl https://api.blaxel.ai/v0/sandboxes/my-sandbox/snapshots \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace"
```
List all your snapshots to also get the ones whose source sandbox no longer exists:
```typescript TypeScript theme={null}
import { Snapshot } from "@blaxel/core";
const snapshots = await Snapshot.list({ limit: 50 });
for await (const snapshot of snapshots) {
console.log(snapshot.name);
}
```
```python Python theme={null}
import asyncio
from blaxel.core import Snapshot
async def main():
snapshots = await Snapshot.list(limit=50)
async for snapshot in snapshots.auto_paging_iter():
print(snapshot.name)
asyncio.run(main())
```
```shell Blaxel CLI theme={null}
bl get snapshots
```
The list endpoint is cursor-paginated and supports full-text search on the snapshot name, source name, and status through the `q` parameter. Refer to the [API reference documentation](/api-reference/introduction#pagination) for how to page through the results.
## Restore a sandbox to a snapshot
Restoring rolls a sandbox back to one of its own snapshots, in place. The sandbox keeps its name, its URLs, its previews, and its configuration: only the instance behind it is torn down and rebuilt from the snapshot, on the same machine the snapshot was taken on.
Everything the sandbox wrote after the snapshot was taken is lost: files, running processes, and memory state. Take a new snapshot first if you want to be able to come back to the current state.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
await sandbox.snapshots.restore("my-snapshot");
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
await sandbox.snapshots.restore("my-snapshot")
```
```bash HTTP API theme={null}
curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/snapshots/my-snapshot/restore \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace"
```
The restore returns as soon as it is accepted, without waiting for the sandbox to be back up. The sandbox answers again once its instance has been rebuilt, so retry your first calls while it resumes.
Only a snapshot of that same sandbox can be restored, and only once it is `ready`. To roll back onto a different sandbox, [fork from the snapshot](#fork-from-a-snapshot) instead.
You can also restore from the Blaxel console: open your sandbox, go to the **Snapshots** tab, and use the **Restore** action on the snapshot you want to go back to.
## Fork a sandbox
Forking creates a new sandbox from an existing one, forking its entire memory state (files + running processes). The fork inherits the source sandbox's image, memory configuration, environment variables, and port settings.
Calling `fork()` automatically snapshots the source sandbox's current state and forks from it. You don't need to create a snapshot first.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
const result = await sandbox.fork("my-sandbox-copy");
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
result = await sandbox.fork("my-sandbox-copy")
```
```bash CLI theme={null}
bl fork sbx/my-sandbox sbx/my-sandbox-copy
```
```bash HTTP API theme={null}
curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/fork \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d '{
"targetType": "sandbox",
"targetName": "my-sandbox-copy"
}'
```
### Fork from a snapshot
To create a sandbox from a snapshot you took earlier, pass its name as `snapshotId`. The fork is created from that snapshot instead of the source sandbox's current state:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
const result = await sandbox.fork("my-sandbox-copy", {
targetType: "sandbox",
snapshotId: "my-snapshot",
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
result = await sandbox.fork(
"my-sandbox-copy",
target_type="sandbox",
snapshot_id="my-snapshot",
)
```
```bash HTTP API theme={null}
curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/fork \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d '{
"targetType": "sandbox",
"targetName": "my-sandbox-copy",
"snapshotId": "my-snapshot"
}'
```
A snapshot carries the image, the memory size, the ports, the infrastructure generation, the region, and the volumes of what it captured, so you can also fork it directly by its `id`, even after its source sandbox is deleted:
```typescript TypeScript theme={null}
import { Snapshot } from "@blaxel/core";
const snapshot = await Snapshot.get(snapshotId);
const result = await snapshot.fork("my-new-sandbox");
```
```python Python theme={null}
from blaxel.core import Snapshot
snapshot = await Snapshot.get(snapshot_id)
result = await snapshot.fork("my-new-sandbox")
```
```bash HTTP API theme={null}
curl -X POST https://api.blaxel.ai/v0/snapshots/$SNAPSHOT_ID/fork \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d '{
"targetType": "sandbox",
"targetName": "my-new-sandbox"
}'
```
Set `targetType` to `application` to start an [application](/Applications/Overview) from the snapshot instead of a sandbox.
When the source sandbox still exists, the fork also inherits what the snapshot does not carry, such as environment variables, lifecycle policies, and network configuration. When the source sandbox is gone, the fork runs with the configuration stored on the snapshot.
### Fork with new environment variables
Pass `envs` to give the fork a different environment from its source. Each variable the source already carries takes the value you pass, each variable it does not carry is added, and every other variable of the source is inherited. The source sandbox is left untouched.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
const result = await sandbox.fork("my-sandbox-copy", {
envs: [
{ name: "NODE_ENV", value: "staging" },
{ name: "FEATURE_FLAG", value: "1" },
],
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
from blaxel.core.client.models.env import Env
sandbox = await SandboxInstance.get("my-sandbox")
result = await sandbox.fork(
"my-sandbox-copy",
envs=[
Env(name="NODE_ENV", value="staging"),
Env(name="FEATURE_FLAG", value="1"),
],
)
```
```bash HTTP API theme={null}
curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/fork \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d '{
"targetType": "sandbox",
"targetName": "my-sandbox-copy",
"envs": [
{ "name": "NODE_ENV", "value": "staging" },
{ "name": "FEATURE_FLAG", "value": "1" }
]
}'
```
The fork starts with the new environment already applied, so processes you launch in it read the new values. Processes that were already running in the source when it was snapshotted keep the environment they were started with, as an environment change cannot reach a process that is already running.
`envs` only sets and adds variables. A variable of the source cannot be removed through a fork.
`envs` works the same way when forking from a snapshot.
### Fork with an expiration policy
A fork expires like its source: it inherits the source's `lifecycle` expiration policies, along with its `ttl` and `expires`. Pass `lifecycle` to give the fork its own expiration policies instead. The fork is then held to those policies alone, without the source's `ttl` or `expires`. See [Sandbox expiration](/Sandboxes/Expiration) for the available policies.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
const result = await sandbox.fork("my-sandbox-copy", {
lifecycle: {
expirationPolicies: [
{ type: "ttl-max-age", value: "45m", action: "delete" },
],
},
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
result = await sandbox.fork(
"my-sandbox-copy",
lifecycle={
"expirationPolicies": [
{"type": "ttl-max-age", "value": "45m", "action": "delete"},
],
},
)
```
```bash HTTP API theme={null}
curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/fork \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d '{
"targetType": "sandbox",
"targetName": "my-sandbox-copy",
"lifecycle": {
"expirationPolicies": [
{ "type": "ttl-max-age", "value": "45m", "action": "delete" }
]
}
}'
```
`lifecycle` works the same way when forking from a snapshot.
### Fork request parameters
| Parameter | Type | Description |
| - | - | - |
| `targetType` | string | Target resource type: `sandbox` or `application` |
| `targetName` | string | Name of the resource to create |
| `port` | integer | Port to expose |
| `snapshotId` | string | Name of the snapshot to fork from, instead of the current state |
| `envs` | array | Environment variables the fork runs with, on top of the ones inherited from the source |
| `lifecycle` | object | Expiration policies of the fork, instead of the ones inherited from the source. Takes `expirationPolicies`, like the sandbox's [`lifecycle`](/Sandboxes/Expiration) |
## Delete a snapshot
Delete a snapshot by its name from the sandbox it was captured from:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
await sandbox.snapshots.delete("my-snapshot");
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
await sandbox.snapshots.delete("my-snapshot")
```
```bash HTTP API theme={null}
curl -X DELETE https://api.blaxel.ai/v0/sandboxes/my-sandbox/snapshots/my-snapshot \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace"
```
Or by its `id`, which also works once the sandbox is deleted:
```typescript TypeScript theme={null}
import { Snapshot } from "@blaxel/core";
await Snapshot.delete(snapshotId);
```
```python Python theme={null}
from blaxel.core import Snapshot
await Snapshot.delete(snapshot_id)
```
```bash HTTP API theme={null}
curl -X DELETE https://api.blaxel.ai/v0/snapshots/$SNAPSHOT_ID \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace"
```
```shell Blaxel CLI theme={null}
bl delete snapshot $SNAPSHOT_ID
```
Deleting a snapshot is the only thing that removes it. Snapshots have no expiration, and Blaxel never prunes them for you.
## Snapshots and sandbox deletion
Deleting a sandbox does not delete its snapshots. They keep their names and stay forkable, with `source.deleted` set to `true` to tell you their sandbox is gone.
Snapshots keep consuming storage after their source sandbox is deleted, and they count against your workspace snapshot quota. Delete the snapshots you no longer need. Refer to [quotas](/Security/Quotas) for the limits that apply to your workspace.
Keep a sandbox's filesystem while shutting it down, and get it back later.
Learn more about sandbox lifecycle and configuration.
# Log streaming
Source: https://docs.blaxel.ai/Sandboxes/Log-streaming
Retrieve process output logs from sandboxes in batch or real-time streaming mode using the Blaxel SDK in TypeScript, Python, or Go.
Logging provides developers with visibility into process outputs within sandboxes. You can retrieve logs either in batch or streaming.
Complete code examples demonstrating all operations are available on Blaxel's GitHub: [in TypeScript](https://github.com/blaxel-ai/sdk-typescript/tree/main/tests/sandbox), [in Python](https://github.com/blaxel-ai/sdk-python/tree/main/tests/integration/sandbox), and [in Go](https://github.com/blaxel-ai/sdk-go/tree/main/integration_tests).
The Blaxel SDK requires two environment variables to authenticate:
| Variable | Description |
| - | - |
| `BL_WORKSPACE` | Your Blaxel workspace name |
| `BL_API_KEY` | Your Blaxel API key |
You can create an API key from the [Blaxel console](https://app.blaxel.ai/profile/security). Your workspace name is visible in the URL when you log in to the console (e.g. `app.blaxel.ai/{workspace}`).
Set them as environment variables or add them to a `.env` file at the root of your project:
```bash theme={null}
export BL_WORKSPACE=my-workspace
export BL_API_KEY=my-api-key
```
The Blaxel SDK does not accept credentials as constructor arguments. Credentials must come from environment variables, a `.env` file, or a local CLI login session (see below).
When developing locally, you can also **log in to your workspace with Blaxel CLI** (as shown above). This allows you to run Blaxel SDK functions that will automatically connect to your workspace without additional setup. When you deploy on Blaxel, authentication is handled automatically — no environment variables needed.
## In batch
### Retrieve from the execution object
Logs for a [process](/Sandboxes/Processes) are available in the *process execution* object **if** the process is started with the `waitForCompletion: true` / `"wait_for_completion": True` parameter.
Both standard output (stdout) and standard error (stderr) are surfaced. `logs` holds both, and `stdout` and `stderr` hold each one separately:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
const process = await sandbox.process.exec({
name: "hello-process",
command: "echo 'Hello, World!'",
waitForCompletion: true
});
console.log(process.logs);
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
process = await sandbox.process.exec({
"name": "hello-process",
"command": "echo 'Hello, World!'",
"waitForCompletion": True
})
print(process.logs)
```
Process execution logs are also visible in the Blaxel Console. Refer to the **Logs** section of the sandbox detail page, as shown below:
### Retrieve from a completed process name or ID
Retrieve logs for a specific [process](/Sandboxes/Processes) (using either its name or process ID) after it has completed execution. By default, this retrieves standard output (**stdout**) only:
```typescript TypeScript theme={null}
const process = await sandbox.process.exec({
name: "hello-process",
command: "echo 'Hello, World!'"
});
const logs = await sandbox.process.logs("hello-process");
```
```python Python theme={null}
process = await sandbox.process.exec({
"name": "hello-process",
"command": "echo 'Hello, World!'"
})
logs = await sandbox.process.logs("hello-process")
```
To retrieve standard error (**stderr**):
```typescript TypeScript theme={null}
const errorLogs = await sandbox.process.logs("hello-process", "stderr");
```
```python Python theme={null}
error_logs = await sandbox.process.logs("hello-process", "stderr")
```
To retrieve both *stderr* and *stdout*:
```typescript TypeScript theme={null}
const allLogs = await sandbox.process.logs("hello-process", "all");
```
```python Python theme={null}
all_logs = await sandbox.process.logs("hello-process", "all")
```
## Streaming
### Retrieve via a callback function
The callback handlers receive log entries in real-time as they're generated by the process:
* **onLog/on\_log**: Receives complete log objects with additional metadata
This method ensures you get a full view, as it first **backfills with all past logs** before beginning the real-time stream.
This approach is ideal for long-running processes where you need to monitor progress or respond to specific log events during execution.
```typescript TypeScript theme={null}
await sandbox.process.exec({
name: "streaming-demo",
command: "echo 'Starting process'; sleep 2; echo 'Processing...'; sleep 2; echo 'Completed!'",
onLog: (log) => {
console.log(`LOG: ${JSON.stringify(log)}`);
}
});
```
```python Python theme={null}
def on_log(log):
print(f"LOG: {log}")
await sandbox.process.exec({
"name": "streaming-demo",
"command": "echo 'Starting process'; sleep 2; echo 'Processing...'; sleep 2; echo 'Completed!'",
"on_log": on_log
})
```
### Retrieve from a process name or ID
Stream logs for a specific [process](/Sandboxes/Processes) (using either its name or process ID):
```typescript TypeScript theme={null}
// Start a long-running process
await sandbox.process.exec({
name: "stream-demo",
command: "sh -c 'for i in $(seq 1 5); do echo \"Output $i\"; sleep 1; done'"
});
const stream = sandbox.process.streamLogs("stream-demo", {
onLog: (log) => console.log("Log:", log),
onStdout: (stdout) => console.log("Stdout:", stdout),
onStderr: (stderr) => console.log("Stderr:", stderr)
});
// Wait for completion and cleanup
await sandbox.process.wait("stream-demo");
stream.close();
```
```python Python theme={null}
# Start a long-running process
await sandbox.process.exec({
"name": "stream-demo",
"command": "sh -c 'for i in $(seq 1 5); do echo \"Output $i\"; sleep 1; done'"
})
stream = sandbox.process.stream_logs("stream-demo", {
"on_log": lambda log: print(f"Log: {log}"),
"on_stdout": lambda stdout: print(f"Stdout: {stdout}"),
"on_stderr": lambda stderr: print(f"Stderr: {stderr}")
})
# Wait for completion and cleanup
await sandbox.process.wait("stream-demo")
stream["close"]()
```
# Sandbox MCP server
Source: https://docs.blaxel.ai/Sandboxes/MCP
Every Blaxel sandbox exposes a built-in MCP server so agents can operate the sandbox using tool calls for files, processes, ports, and previews.
Every sandbox is exposed via an MCP server that allows agents to **operate it using tool calls.**
## Connect to the MCP server
The MCP server operates through streamable HTTP at the sandbox's base URL:
```text theme={null}
https:///mcp
```
The base URL of a sandbox can be [retrieved](https://docs.blaxel.ai/api-reference/compute/get-sandbox) via the management API of sandboxes under `metadata.url` or via the Blaxel [SDKs](/Sandboxes/Overview#retrieve-an-existing-sandbox).
Connect to this MCP server [like any other MCP server](../Functions/Invoke-functions) through the endpoint shown above. Instructions for some popular applications are provided below.
You must provide your [Blaxel API key](../Security) in the `Authorization` header. If your user has access to multiple workspaces, include the workspace header as well.
* Required: `Authorization: Bearer `
* Optional (multi-workspace): `X-Blaxel-Workspace: `
Bearer-token authentication only — there is no OAuth for connecting to the sandbox MCP endpoint. See [MCP endpoints at a glance](/skills-mcp#mcp-endpoints-at-a-glance) for how this compares to the other MCP surfaces on Blaxel.
### Add to Cursor
Add the server to `~/.cursor/mcp.json`:
```json theme={null}
{
"mcpServers": {
"blaxel": {
"url": "https:///mcp",
"headers": {
"Authorization": "Bearer ",
"X-Blaxel-Workspace": ""
}
}
}
}
```
### Add to Claude Code
[Add the remote HTTP server to your Claude Code](https://docs.anthropic.com/en/docs/claude-code/mcp#option-3%3A-add-a-remote-http-server) by running the following command:
```bash theme={null}
claude mcp add --transport http blaxel https:///mcp \\
--header "Authorization: Bearer " \\
--header "X-Blaxel-Workspace: "
```
[Follow this tutorial to build a Claude Agent SDK-powered agent that connects to a Blaxel sandbox and operates it using the sandbox's MCP server](../Tutorials/Claude-Agent-SDK-MCP).
### Add to Windsurf
Add to `~/.codeium/windsurf/mcp_config.json`:
```json theme={null}
{
"mcpServers": {
"blaxel": {
"url": "https:///mcp",
"headers": {
"Authorization": "Bearer ",
"X-Blaxel-Workspace": ""
}
}
}
}
```
### Add to Goose
Add to `~/.config/goose/config.yaml`:
```yaml theme={null}
extensions:
sandbox-mcp:
enabled: true
type: streamable_http
name: sandbox-mcp
description: Sandbox MCP
uri: https:///mcp
envs: {}
env_keys: []
headers:
Authorization: Bearer
X-Blaxel-Workspace:
timeout: 300
bundled: null
available_tools: []
```
## Tools available in the MCP server
### Process management
| Tool | Description |
| - | - |
| `processExecute` | Execute a command with options for timeout, restart, env vars |
| `processGet` | Get info about a process by PID or name |
| `processGetLogs` | Get logs for a specific process |
| `processesList` | List all running processes |
| `processStop` | Stop a specific process |
| `processKill` | Kill a specific process |
### Filesystem operations
| Tool | Description |
| - | - |
| `fsGetWorkingDirectory` | Get the current working directory |
| `fsListDirectory` | List contents of a directory |
| `fsReadFile` | Read contents of a file |
| `fsWriteFile` | Create or update a file |
| `fsDeleteFileOrDirectory` | Delete a file or directory |
### Code search and navigation
| Tool | Description |
| - | - |
| `codegenCodebaseSearch` | Semantic search to find relevant code snippets |
| `codegenFileSearch` | Fast fuzzy file path search |
| `codegenGrepSearch` | Exact regex search using ripgrep engine |
| `codegenListDir` | List directory contents (quick discovery) |
| `codegenReadFileRange` | Read file contents within a specific line range (max 250 lines) |
| `codegenRerank` | Performs semantic search/reranking on code files in a directory |
### Code editing
| Tool | Description |
| - | - |
| `codegenEditFile` | Propose and apply a [targeted edit to a file](/Sandboxes/Codegen-tools) (⚠️ requires Morph or Relace API key) |
| `codegenParallelApply` | Plan parallel edits across multiple file locations |
| `codegenReapply` | Use smarter model to retry a failed edit |
Using Blaxel SDK, you can retrieve the tools for a sandbox in any supported framework format by passing the sandbox’s name. For example, in LangGraph:
```typescript TypeScript theme={null}
import { blTools } from "@blaxel/vercel";
const tools = await blTools([`sandbox/${sandboxName}`])
```
```python Python theme={null}
from blaxel.langgraph import bl_tools
tools = await bl_tools([f"sandbox/{sandbox_name}"])
```
[Read more documentation](../Functions/Invoke-functions) on connecting to the MCP server directly from your code.
## Example: Connect to a sandbox from Claude Agent SDK
Build a Claude Agent SDK agent that connects to a Blaxel sandbox and operates it using the sandbox's MCP server.
# Run sandbox workloads as a non-root user
Source: https://docs.blaxel.ai/Sandboxes/Non-root-user
Scope everything a sandbox runs on your behalf to an unprivileged user, and refuse calls from that user to the sandbox API.
By default, everything inside a sandbox runs as `root`. You can instead declare an unprivileged user in your image, so processes, terminals and filesystem operations run as that user while the sandbox API keeps the privileges it needs for drive mounts, networking and keep-alive.
## Why the sandbox API stays root
The sandbox API is PID 1 in the sandbox. It mounts [Agent Drive](/Agent-drive/Overview), configures egress networking, installs the CA bundle and manages standby, and all of those require root.
This is why a plain `USER` directive is not enough on its own: in a plain container it de-privileges the sandbox API itself, which breaks everything mentioned above. On Blaxel, the runtime does not apply the image `USER` to the sandbox API. It starts the API as root and passes your user down to it, and the API drops privileges for the operations it runs on your behalf, keeping root only for the infrastructure work listed below.
Running the same image locally with `docker run` applies `USER` to the sandbox API, so drive mounts and networking setup fail. Use the entrypoint variant below if you need the image to behave the same way outside Blaxel.
This scopes privileges inside the sandbox. It is a defense-in-depth measure: the microVM boundary remains the isolation guarantee between your sandbox and everything else.
## Declare a user in your image
Create the user in your Dockerfile, declare it with `USER`, and turn the feature on with `BL_SANDBOX_USER_ENABLED`:
```docker theme={null}
FROM node:24-alpine
RUN adduser -D -u 10001 -h /blaxel app
WORKDIR /blaxel
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
USER app
ENV HOME=/blaxel
ENV BL_SANDBOX_USER_ENABLED=true
ENTRYPOINT ["/usr/local/bin/sandbox-api"]
```
The user follows Docker `USER` syntax: `app`, `10001`, `app:app` or `10001:10001`. The user must exist in the image, and it cannot resolve to uid 0. Make sure your workload owns every directory it writes to.
### Pass the user explicitly
If you need root-only preparation at boot, such as a `chown` or a `mkdir` outside the user's home, use an entrypoint that runs as root and hands over to the sandbox API with the `--user` flag:
```bash theme={null}
#!/bin/sh
set -eu
chown -R app /blaxel
exec /usr/local/bin/sandbox-api --user app "$@"
```
Leave `USER` out of that Dockerfile so the entrypoint itself runs as root, and point `ENTRYPOINT` at the script. The `--user` flag is the explicit opt-in, so it does not need `BL_SANDBOX_USER_ENABLED`. Setting `BL_SANDBOX_USER` in your image environment is equivalent to `USER`, and still requires the enable variable.
## What runs as your user vs. stays root
| Runs as your user | Stays root |
| - | - |
| Processes, startup and background commands | Agent Drive mounts |
| Terminal sessions | Egress and networking setup |
| Code execution | CA bundle installation |
| Filesystem API calls (read, write, upload, search) | Keep-alive and standby control |
Calls to the sandbox API from inside a sandbox process keep the same identity. There is no parameter or environment variable that asks for root, so a process cannot use the API to escalate its own privileges. To refuse those calls altogether, [isolate the sandbox API](#isolate-the-sandbox-api).
## Isolate the sandbox API
Calls to the sandbox API through Blaxel require credentials. Calls from inside the sandbox to `localhost:8080` do not. Set `BL_SANDBOX_API_ISOLATION` to refuse connections from your workload to the sandbox API, so code running in the sandbox cannot use it to run commands, read files or open terminals:
```docker theme={null}
USER app
ENV HOME=/blaxel
ENV BL_SANDBOX_API_ISOLATION=true
```
`BL_SANDBOX_API_ISOLATION` also runs your workload as the image `USER`, without `BL_SANDBOX_USER_ENABLED`.
At boot, before your workload starts, the sandbox API installs firewall rules on its port. Refused connections get a TCP reset. The sandbox does not start when the rules cannot be installed.
| Caller | Result |
| - | - |
| Authenticated calls through Blaxel (SDK, CLI, console, API) | Allowed |
| Root processes inside the sandbox | Allowed |
| Non-root processes inside the sandbox, on `localhost`, `127.0.0.1`, `::1` or any address of the sandbox | Refused |
| Containers, tunnels or bridges running inside the sandbox | Refused |
Only the sandbox API port is filtered. [Preview URLs](/Sandboxes/Preview-url) and your application ports are not affected.
To check it, open a terminal in the sandbox and run the following commands. The image needs `curl`.
```bash theme={null}
id -un
curl localhost:8080/health
```
`id -un` prints your image user, and `curl` fails with `Connection refused`. The same call through Blaxel with your credentials still succeeds.
Without a `USER` directive, or with `USER root`, the sandbox API installs the rules, logs a warning and runs your workload as root.
A workload running as root can bypass API isolation. Root callers are allowed by the rules, and root can remove them or stop the sandbox API. API isolation only protects the sandbox API from workloads running as a non-root `USER`.
## Limitations
* Access granted only through a supplementary group of your user is not honored on filesystem API calls, which apply the primary group.
* Files that already exist in the image and belong to root stay read-only for your workload. Change their ownership at build time or in a root entrypoint.
* With neither `BL_SANDBOX_USER_ENABLED` nor `BL_SANDBOX_API_ISOLATION` set, the sandbox runs everything as root.
Create reusable sandbox images with Dockerfiles.
Pass configuration and secrets to a sandbox.
# Sandboxes
Source: https://docs.blaxel.ai/Sandboxes/Overview
Run LLM-generated code in lightweight, sandboxed virtual machines with sub-25ms cold starts, file system, processes, and an MCP server for agents.
Sandboxes are instant-launching virtual machines serving as **sandboxed compute runtimes for agents**. You can securely run LLM-generated code inside these VMs, making them ideal for agents that need access to an operating system to run commands with no risk of escaping.
They provide a basic [REST API interface](https://docs.blaxel.ai/api-reference/filesystem) for accessing the file system and processes, along with an [MCP server](./MCP) that makes these capabilities available as tool calls.
## Sandbox lifecycle
Unlike traditional sandbox infrastructure, Blaxel's standout feature is its fully managed lifecycle. Sandboxes resume from standby in under 25 milliseconds and automatically scale to zero after a few seconds of inactivity. This means that your sandboxes wait on standby indefinitely when not used, eliminating cold starts without complex orchestration. Memory state is maintained even after scaling down, including the running processes and entire filesystem. For cost-effective long-term persistence, you can attach [volumes](/Sandboxes/Volumes) to sandboxes. For shared filesystems mountable across multiple sandboxes, see [Agent Drive](/Agent-drive/Overview).
[Read more about the Blaxel sandbox lifecycle](https://blaxel.ai/blog/understand-the-lifecycle-of-a-blaxel-sandbox).
### Active mode
Sandboxes stay in active mode as long as there's an active connection to them.
You are charged for memory (based on how much you allocated at sandbox creation) and storage while a sandbox is in active mode. CPU resources are allocated accordingly by Blaxel based on your selected memory allocation and are not charged separately. For example, a sandbox with 8 GB memory is allocated 4 CPU cores, while one with 16 GB memory is allocated 6 CPU cores. [Learn more about pricing](https://blaxel.ai/pricing).
### Standby mode
If a sandbox does not have an active connection, it automatically transitions to a warm standby mode (also called *scale-to-zero*). This transition happens in approximately 15 seconds.
If a sandbox has an ongoing WebSocket/TCP connection, there is a 15-minute idle timeout for such a connection. This means that if the WebSocket/TCP connection remains idle for 15 minutes with no activity, the sandbox will automatically transition to standby mode.
When transitioning to standby mode, Blaxel automatically creates a snapshot of the entire state (including the complete file system in memory, preserving both files and running processes) and transitions the sandbox to standby mode within approximately 15 seconds.
Reconnecting to the sandbox transitions it back to active mode. Any running processes are included in this snapshot and will be instantly restored when you reconnect to the sandbox.
The snapshot preserves process and filesystem state, but not external network connections, such as database connections, message queues, or HTTP connection pools, which will automatically time out and close on the remote side of the connection.
You are not charged for memory while a sandbox is in standby mode. However, you are charged for the storage of the snapshot and/or the volumes. [Learn more about pricing](https://blaxel.ai/pricing).
To prevent a sandbox from going into standby, you can use [process keep-alive](./Processes#sandbox-keep-alive), [WebSocket ping/pong messages](./Standby-control) or [other techniques](./Standby-control) to prevent the timeout.
### Archived mode
Standby keeps a sandbox instantly resumable, memory included, and you keep paying for the storage of its snapshot. When a sandbox is not needed for a long time, [archiving](./Archive) it stores the filesystem changes it made over its image and shuts it down: memory is not preserved, and nothing is kept warm for it.
An archived sandbox keeps its name, its configuration, and its preview URLs. Unarchiving it starts the sandbox again from its image and writes the archived filesystem back over it, so you get the same sandbox back with its files. All processes that were running are restarted automatically from their configuration, with new process IDs.
Archiving differs from standby: standby resumes in milliseconds with memory and processes intact, while unarchiving takes as long as the archived filesystem needs to be written back. Memory is not restored; all processes that were running are restarted automatically. Archiving is also not a [snapshot](./Fork): a snapshot can be forked into other sandboxes, while an archive only ever restores the sandbox it was taken from.
### Deletion
In addition to automatic scale-to-zero, Blaxel also supports [automatic sandbox deletion based on expiration policies](./Expiration). Starter quotas enforce time-to-live limits (TTLs), while higher quota tiers unlock sandboxes with unlimited persistence.
Automatic deletion differs from automatic standby (*scale-to-zero*). Deleted sandboxes cannot be recovered, while sandboxes in standby mode can be resumed instantly.
### Sandbox deployment statuses
During the deployment process, the possible sandbox statuses are:
* `UPLOADING`: A new sandbox version has just been uploaded; the build has not started yet.
* `BUILDING`: A new sandbox version has been uploaded and the build is in progress.
* `DEPLOYING`: The sandbox deployment is in progress.
* `DEPLOYED`: The sandbox is ready to use. It is either in active mode or standby mode.
* `FAILED`: An error occurred during the build or deployment of the sandbox.
* `DEACTIVATING`: The sandbox is being disabled, either because it was explicitly disabled or because a previous revision is being superseded by a new deployment.
* `DEACTIVATED`: The sandbox has been disabled and is not accepting connections.
* `TERMINATED`: A TTL was set for the sandbox; it has been deleted and will be removed from the API/UI soon.
* `DELETING`: A deletion request has been triggered and the deletion is in progress.
* `ARCHIVING`: The filesystem is being exported to the archive store and the sandbox is about to be shut down.
* `ARCHIVED`: The filesystem is stored and the sandbox is shut down. [Unarchive](./Archive) it to use it again.
* `UNARCHIVING`: The sandbox is running again and its archived filesystem is being restored.
`UPLOADING/BUILDING` statuses only appear when using `bl deploy` and `bl push` from a sandbox image folder.
## Use cases
Some examples of use cases include:
* **Code review agents** that analyze repositories to detect the effects of changes. These agents run fully isolated compute environments for each tenant while keeping them snapshotted in standby between sessions, eliminating the need to clone the repo every time.
* **Code generation agents** that iterate in their own compute environments, and instantly render live application previews as human users build, step away, and log back in.
* **Data analyst agents** that execute ad hoc data analysis workflows, generating scripts on-the-fly and running them securely against private files or data within an isolated, ZDR-compliant environment.
* **Background agents** that operate beyond their pre-configured tools. Each agent gets its own "personal computer" where it can autonomously install packages, execute custom scripts, store files, and adapt to new requirements securely. They can parallelize dozens of those personal computers.
## Create a sandbox
### Using the SDKs
Create a new sandbox using the [Blaxel SDK](../sdk-reference/introduction) by specifying a name, image to use, [deployment region](../Infrastructure/Regions), optional labels, and the ports to expose. Note that **ports** **80** (system) and **443** & **8080** (sandbox API) are reserved by Blaxel.
The Blaxel SDK requires two environment variables to authenticate:
| Variable | Description |
| - | - |
| `BL_WORKSPACE` | Your Blaxel workspace name |
| `BL_API_KEY` | Your Blaxel API key |
You can create an API key from the [Blaxel console](https://app.blaxel.ai/profile/security). Your workspace name is visible in the URL when you log in to the console (e.g. `app.blaxel.ai/{workspace}`).
Set them as environment variables or add them to a `.env` file at the root of your project:
```bash theme={null}
export BL_WORKSPACE=my-workspace
export BL_API_KEY=my-api-key
```
The Blaxel SDK does not accept credentials as constructor arguments. Credentials must come from environment variables, a `.env` file, or a local CLI login session (see below).
When developing locally, you can also **log in to your workspace with Blaxel CLI** (as shown above). This allows you to run Blaxel SDK functions that will automatically connect to your workspace without additional setup. When you deploy on Blaxel, authentication is handled automatically — no environment variables needed.
The recommended pattern for sandbox creation is `createIfNotExists()` / `create_if_not_exists()`. Blaxel first checks for an existing sandbox with the provided `name` and either retrieves it or creates a new one using your specified configuration.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
// Create sandbox if it doesn't exist
const sandbox = await SandboxInstance.createIfNotExists({
name: "my-sandbox",
image: "blaxel/base-image:latest", // public or custom image
memory: 4096, // in MB
ports: [{ target: 3000, protocol: "HTTP" }], // optional; ports to expose
labels: { env: "dev", project: "my-project" }, // optional; labels
region: "us-pdx-1" // deployment region
});
```
```python Python theme={null}
import asyncio
from blaxel.core import SandboxInstance
async def main():
# Create sandbox if it doesn't exist
sandbox = await SandboxInstance.create_if_not_exists({
"name": "my-sandbox",
"image": "blaxel/base-image:latest", # public or custom image
"memory": 4096, # in MB
"ports": [{ "target": 3000 }], # optional; ports to expose
"labels": {"env": "dev", "project": "my-project"}, # optional; labels
"region": "us-pdx-1" # deployment region
})
if __name__ == "__main__":
asyncio.run(main())
```
Attach [volumes](./Volumes) in the create call with `volumes: [{ name: "my-volume", mountPath: "/mnt/data" }]` (Python: `"volumes": [{"name": "my-volume", "mount_path": "/mnt/data"}]`). Drives cannot be attached at creation. Create or get a drive in the sandbox's region, then call `sandbox.drives.mount(...)` as shown in [Mount a drive to a sandbox](/Agent-drive/Overview#mount-a-drive-to-a-sandbox).
For [real-time previews](./Preview-url#blaxel-sdk), use a constant preview name such as `"app"` with `sandbox.previews.createIfNotExists()` / `create_if_not_exists()`. Names are scoped to each sandbox. Omit `prefixUrl` to get a stable URL without generating your own prefix.
An alternative is to use `create()`, which raises an error when a sandbox with the specified name already exists:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.create({
name: "my-sandbox",
image: "blaxel/base-image:latest", // public or custom image
memory: 4096, // in MB
ports: [{ target: 3000, protocol: "HTTP" }], // optional; ports to expose
labels: { env: "dev", project: "my-project" }, // optional; labels
region: "us-pdx-1" // deployment region
});
```
```python Python theme={null}
import asyncio
from blaxel.core import SandboxInstance
async def main():
sandbox = await SandboxInstance.create({
"name": "my-sandbox",
"image": "blaxel/base-image:latest", # public or custom image
"memory": 4096, # in MB
"ports": [{ "target": 3000 }], # optional; ports to expose
"labels": {"env": "dev", "project": "my-project"}, # optional; labels
"region": "us-pdx-1" # deployment region
})
if __name__ == "__main__":
asyncio.run(main())
```
### Using the CLI and Console
It is also possible to create a sandbox via the Blaxel CLI or the Blaxel Console.
```shell theme={null}
bl new sandbox my-sandbox
cd my-sandbox
bl deploy
```
This command initializes a new sandbox project and configuration file in the named directory `my-sandbox` and then deploys the sandbox on Blaxel.
The project directory contains the Blaxel configuration file `blaxel.toml`, which can be further customized to suit your sandbox deployment requirements, by modifying the base image, memory, environment, etc. [`Learn more about the blaxel.toml file`](/deployment-reference).
Running `bl deploy` here also saves the image to be reused later. As an alternative, you can use `bl push` to push the image to Blaxel's registry. `bl push` only publishes the image; it does not create a new sandbox deployment, update existing deployments, or restart running processes.
* Log in to the [Blaxel Console](https://app.blaxel.ai/).
* Click **Sandboxes** in the left navigation menu.
* Click **+Create sandbox** -> **Deploy from console**.
* Select an image.
* On the next page, configure the sandbox resources, including memory, time-to-live and region.
* Click **Next** to proceed.
* On the next page, reconfirm the sandbox parameters. Click **Create** to create the sandbox.
### Using the API
Although less common, it is also possible to [create a sandbox by directly interacting with the Blaxel REST API](/Sandboxes/Templates#blaxel-api).
## Understand sandbox configuration
### Images
Blaxel provides [pre-built sandbox images](/Sandboxes/Templates#pre-built-images) for common needs (e.g. `blaxel/nextjs:latest`, `blaxel/py-app:latest`).
It is also possible to build your own [custom images](/Sandboxes/Templates) to create sandboxes with a consistent, customized set of tools, configurations, or entrypoint scripts.
### Memory and filesystem
For maximum performance, Blaxel sandboxes store part of their filesystem in memory. The base of the filesystem (the user-supplied image) is stored as read-only files on host storage using a highly efficient format called [EROFS (Extendable Read-Only File System)](https://en.wikipedia.org/wiki/EROFS). On top of the read-only base, a writable layer lives entirely in the sandbox's RAM using `tmpfs`. [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) serves as orchestrator, directing reads to the EROFS base and writes to the in-memory `tmpfs` filesystem.
Due to this, Blaxel sandboxes reserve, when possible, approximately 50% of the available memory for the `tmpfs` filesystem. More information on our implementation is available in this [blog post](https://blaxel.ai/blog/how-to-slash-sandbox-memory-usage-by-75-using-overlayfs).
Use [volumes](/Sandboxes/Volumes) or [Agent Drive](/Agent-drive/Overview) when data needs to live at a specific mount path or persist independently from the sandbox lifecycle. Volumes require deleting and recreating the sandbox first, while drives can be hot-mounted/-unmounted. Both are slower than the local sandbox filesystem. Refer to our [blog post on choosing the right storage for your Blaxel agents](https://blaxel.ai/blog/choose-the-right-storage-for-your-blaxel-agents) for more information.
### Ports
The following ports are reserved by Blaxel's system:
* **443**: This port hosts the main [sandbox API](https://docs.blaxel.ai/api-reference/filesystem/get-file-or-directory-information) and is exposed via HTTPS
* **80**: Reserved for system operations
* **8080**: Reserved for sandbox API functionality
You can expose specific non-reserved ports [**when creating a new sandbox**](/Sandboxes/Overview) by using the `ports` parameter. This allows you to access these ports from outside the sandbox via the sandbox API.
You only need to expose ports at sandbox creation time if you plan to access the port via the sandbox API. For access via [preview URL](./Preview-url), ports are dynamically opened as needed.
### Regions
Select the region where you want to deploy your sandbox. The `region` parameter is optional but recommended, and can reference any of the [supported region codes](/Infrastructure/Regions).
It is also possible to set a default region using the `BL_REGION` environment variable (e.g. in your `.env` file) to avoid repeating it in every SDK call.
The `region` parameter is currently optional but will become mandatory in future versions of the Blaxel SDK. To ensure compatibility, it is highly recommended to always specify a deployment region, either using the `region` parameter in your SDK call or via the `BL_REGION` environment variable.
### Labels
You can also add optional labels for sandboxes. Labels are specified as key-value pairs during sandbox creation.
```typescript TypeScript theme={null}
const sandbox = await SandboxInstance.createIfNotExists({
name: "my-sandbox",
image: "blaxel/base-image:latest", // public or custom image
memory: 4096, // in MB
labels: { env: "dev", project: "my-project" }, // optional; labels
region: "us-pdx-1" // deployment region
});
```
```python Python theme={null}
sandbox = await SandboxInstance.create_if_not_exists({
"name": "my-sandbox",
"image": "blaxel/base-image:latest", # public or custom image
"memory": 4096, # in MB
"labels": {"env": "dev", "project": "my-project"}, # optional; labels
"region": "us-pdx-1" # deployment region
})
```
You can use these labels for filtering sandboxes in the Blaxel CLI or Blaxel Console:
```shell theme={null}
# Get sandboxes with specific label (e.g., env=dev)
bl get sandboxes -o json | jq -r '.[] | select(.metadata.labels.env == "dev") | .metadata.name'
```
### External ID
External IDs let you tag sandboxes with your own identifier so you can retrieve them later without scanning your entire workspace. Use them to index sandboxes by session ID, user ID, conversation ID, or any caller-owned key.
Requires `@blaxel/core` ≥ 0.2.93 (TypeScript) or `blaxel` ≥ 0.2.57 (Python).
External IDs are backed by a dedicated database index, making lookups fast regardless of how many sandboxes exist in the workspace.
Rules for `externalId`:
* Max 64 characters, alphanumeric and dashes only (`a-z`, `A-Z`, `0-9`, `-`)
* Not unique: multiple sandboxes can share the same value
* Mutable: can be changed after creation
* Omitting it on update preserves the current value
Set `externalId` in metadata at creation time:
```typescript TypeScript theme={null}
const sandbox = await SandboxInstance.createIfNotExists({
name: "my-sandbox",
image: "blaxel/base-image:latest",
externalId: "session-abc-123",
region: "us-pdx-1"
});
```
```python Python theme={null}
sandbox = await SandboxInstance.create_if_not_exists({
"name": "my-sandbox",
"image": "blaxel/base-image:latest",
"external_id": "session-abc-123",
"region": "us-pdx-1"
})
```
Retrieve the most recent non-terminated sandbox matching an external ID:
```typescript TypeScript theme={null}
const sandbox = await SandboxInstance.getByExternalId("session-abc-123");
```
```python Python theme={null}
sandbox = await SandboxInstance.get_by_external_id("session-abc-123")
```
List all sandboxes with a given external ID (paginated, hides terminated sandboxes by default):
```typescript TypeScript theme={null}
const sandboxes = await SandboxInstance.list({ externalId: "session-abc-123" });
```
```python Python theme={null}
sandboxes = await SandboxInstance.list(external_id="session-abc-123")
```
### Kernel networking
By default, sandboxes run on a minimal kernel without iptables support. You can enable iptables by passing `extraArgs` at creation time.
`extraArgs` can only be set when creating a sandbox. It cannot be changed after creation.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.create({
name: "my-sandbox",
extraArgs: { iptables: "enabled" },
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.create({
"name": "my-sandbox",
"extra_args": {"iptables": "enabled"},
})
```
For a complete example of using iptables to run Tailscale inside a sandbox, see the [Tailscale tutorial](/Tutorials/Tailscale).
### Creation timeout
Blaxel gives every sandbox creation a default deadline to become ready before failing it. You can shorten this deadline for a single `create()` or `createIfNotExists()` call with the `timeout` option, expressed in seconds. When the deadline is reached, Blaxel cancels the creation, removes the sandbox, and the call raises a `SandboxCreationTimeoutError`.
Catch that error to handle a slow creation explicitly, for example by reporting it to the user or falling back to another region:
```typescript TypeScript theme={null}
import { SandboxInstance, isCreationTimeoutError } from "@blaxel/core";
try {
const sandbox = await SandboxInstance.createIfNotExists(
{
name: "my-sandbox",
image: "blaxel/base-image:latest",
memory: 4096,
region: "us-pdx-1",
},
{ timeout: 30 } // fail if the sandbox is not ready after 30 seconds
);
} catch (error) {
if (isCreationTimeoutError(error)) {
// The sandbox was removed: log, alert, or fall back to another region.
console.error(`Sandbox ${error.sandboxName} was not ready after ${error.timeout}s`);
return;
}
throw error;
}
```
```python Python theme={null}
from blaxel.core import SandboxCreationTimeoutError, SandboxInstance
try:
sandbox = await SandboxInstance.create_if_not_exists(
{
"name": "my-sandbox",
"image": "blaxel/base-image:latest",
"memory": 4096,
"region": "us-pdx-1",
},
timeout=30, # fail if the sandbox is not ready after 30 seconds
)
except SandboxCreationTimeoutError as error:
# The sandbox was removed: log, alert, or fall back to another region.
print(f"Sandbox {error.sandbox_name} was not ready after {error.timeout}s")
```
`SandboxCreationTimeoutError` exposes the error code (`"CREATION_TIMEOUT"`), the HTTP status (`408`), the sandbox name and the requested `timeout` so you can log or branch on the failure (`code`/`status`/`sandboxName` in TypeScript, `code`/`status_code`/`sandbox_name` in Python, where it is a subclass of `SandboxAPIError`). The sandbox has already been removed when the error is raised, so its name is free again if you decide to create it again.
`timeout` must be a whole number between 1 and 50 seconds. It can only shorten the creation deadline, not extend it.
### Expiration
Blaxel supports [automatic sandbox deletion](./Expiration) based on specific conditions.
## Retrieve an existing sandbox
To reconnect to an existing sandbox, simply provide its name:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
// Connect to existing sandbox
const sandbox = await SandboxInstance.get("my-sandbox");
```
```python Python theme={null}
from blaxel.core import SandboxInstance
# Connect to existing sandbox
sandbox = await SandboxInstance.get("my-sandbox")
```
Complete code examples demonstrating all operations are available on Blaxel's GitHub: [in TypeScript](https://github.com/blaxel-ai/sdk-typescript/tree/main/tests/sandbox), [in Python](https://github.com/blaxel-ai/sdk-python/tree/main/tests/integration/sandbox), and [in Go](https://github.com/blaxel-ai/sdk-go/tree/main/integration_tests).
## List sandboxes
To retrieve all sandboxes in your workspace, use the built-in SDK pagination helper functions.
Pagination is recommended for list operations because retrieving all available resources in a single request is slow and resource-intensive, and therefore does not scale well.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxes = await SandboxInstance.list({ limit: 50 });
for await (const sandbox of sandboxes) {
console.log(sandbox.metadata?.name);
}
```
```python Python theme={null}
import asyncio
from blaxel.core import SandboxInstance
async def main():
sandboxes = await SandboxInstance.list(limit=50)
async for sandbox in sandboxes.auto_paging_iter():
print(sandbox.metadata.name)
asyncio.run(main())
```
Pagination is also available via the Management API list endpoint. For more information, refer to the [API](/api-reference/introduction#pagination) and [SDK](/sdk-reference/introduction#pagination) reference documentation.
## Delete a sandbox
When you delete a sandbox, all data is immediately erased. If the sandbox was never in standby mode, Blaxel guarantees ZDR (zero data retention).
Delete a sandbox by calling:
* the class-level `delete()` method with the sandbox `name` as argument, or
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
// Delete sandbox using class-level method
await SandboxInstance.delete("my-sandbox");
```
```python Python theme={null}
from blaxel.core import SandboxInstance
# Delete sandbox using class-level method
await SandboxInstance.delete("my-sandbox")
```
* the instance-level `delete()` method:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
// Get existing sandbox
const sandbox = await SandboxInstance.get("my-sandbox");
// Delete sandbox using instance-level method
await sandbox.delete()
```
```python Python theme={null}
from blaxel.core import SandboxInstance
# Get existing sandbox
sandbox = await SandboxInstance.get("my-sandbox")
# Delete sandbox using instance-level method
await sandbox.delete()
```
## Upgrade a sandbox's API
Every Blaxel sandbox includes a [custom API binary](./Templates#how-sandbox-images-work), which is necessary for sandbox functionality like process management and file operations. It is possible to perform an in-place upgrade of this API without needing to recreate or restart the sandbox.
This feature is currently in beta and only available for sandboxes built or created with sandbox API v0.2.0 or later (sandboxes created after 2 Feb 2026). For sandboxes built or created earlier than this date/API version, in-place upgrade is not possible; the sandbox must be recreated to obtain the new API.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
// Connect to existing sandbox
const sandbox = await SandboxInstance.get("my-sandbox")
// Upgrade sandbox API to "latest"
await sandbox.system.upgrade({ version: "latest" })
```
```python Python theme={null}
from blaxel.core import SandboxInstance
# Connect to existing sandbox
sandbox = await SandboxInstance.get("my-sandbox")
# Upgrade sandbox API to "latest"
await sandbox.system.upgrade(version="latest")
```
You can find a list of available API versions in [Blaxel's public repository](https://github.com/blaxel-ai/sandbox/).
## Connect to a sandbox with an interactive terminal
You can explore the contents of a sandbox with an interactive terminal. You can access this terminal in two ways:
* From the Blaxel Console, by visiting the detail page for your sandbox in your web browser and selecting the **Terminal** tab:
* From your local host, by running:
```bash theme={null}
bl connect sandbox your-sandbox-name
```
Automatically delete sandboxes based on specific conditions.
Interact with a sandbox through its MCP server.
Set environment variables and secrets in sandboxes.
Execute and manage processes in sandboxes.
Manage directories and files in sandboxes.
Manage codebases with tools specifically designed for code generation.
Access logs generated in a sandbox.
Expose and connect to ports of a sandbox.
Render code in real-time via a direct preview URL.
Manage temporary sessions to connect to sandboxes from a frontend client.
Route outbound traffic through the proxy, configure firewalls, and bind egress IPs.
Attach volumes to sandboxes to persist files.
Create custom sandbox images.
Recommended practices for using Blaxel sandboxes.
Sandboxes work alongside [Agents Hosting](/Agents/Overview), [MCP Servers](/Functions/Overview), and [Batch Jobs](/Jobs/Overview) to form a complete agentic architecture. See [observability](/Observability/Overview) for monitoring sandbox activity.
See tutorials and examples:
Connect a Claude Agent SDK agent to a Blaxel sandbox and operate it using its MCP server.
Read tutorials for running applications built with popular frameworks like Astro, Expo and Next.js on Blaxel.
Or explore the Sandbox API reference:
Access your sandbox with an HTTP REST API.
# Ports
Source: https://docs.blaxel.ai/Sandboxes/Ports
Expose specific TCP ports on a Blaxel sandbox so external clients can connect to web servers, dev servers, or other services running inside it.
Your sandbox, just like any virtual machine, can **expose ports**. These ports let you connect to a process or app running in the sandbox.
## Reserved ports
The following ports are reserved by Blaxel's system:
* **443**: This port hosts the main [sandbox API](https://docs.blaxel.ai/api-reference/filesystem/get-file-or-directory-information) and is exposed via HTTPS
* **80**: Reserved for system operations
* **8080**: Reserved for sandbox API functionality
You can expose and use any other port on your sandbox.
## Expose a port
To whitelist sandbox traffic in your network, you can [retrieve the public IP addresses](/Infrastructure/Regions#public-ip-addresses) used by Blaxel.
You can expose specific non-reserved ports [**when creating a new sandbox**](/Sandboxes/Overview) by using the `ports` parameter. This allows you to access these ports from outside the sandbox via the sandbox API.
You only need to expose ports at sandbox creation time if you plan to access the port via the sandbox API. For access via [preview URL](./Preview-url), ports are dynamically opened as needed.
```typescript TypeScript {8} theme={null}
import { SandboxInstance } from "@blaxel/core";
// Create a new sandbox
const sandbox = await SandboxInstance.create({
name: "my-sandbox",
image: "blaxel/base-image:latest",
memory: 4096,
region: "us-pdx-1",
ports: [{ target: 3000 }]
});
```
```python Python {8} theme={null}
from blaxel.core import SandboxInstance
# Create a new sandbox
sandbox = await SandboxInstance.create({
"name": "my-sandbox",
"image": "blaxel/base-image:latest",
"memory": 4096,
"region": "us-pdx-1",
"ports": [{ "target": 3000 }]
})
```
## Access the sandbox on a specific port
### Via the sandbox API
This option requires your request to be [authenticated](../Security/Access-tokens). You can access your sandbox on a specific port through the sandbox API using the following URL format:
```text theme={null}
https://sbx-{resource_name}-{workspace_id}.{region_id}.bl.run/port/{port_number}
```
For example to connect to port 3000 on `my-sandbox` in workspace `abc123`: `https://sbx-my-sandbox-abc123.us-pdx-1.bl.run/port/3000`
Request paths, if any, can be specified after the port number. For example: `https://sbx-my-sandbox-abc123.us-pdx-1.bl.run/port/3000/my/api/endpoint`
### Via a preview URL
[Preview URLs](/Sandboxes/Preview-url) provide a simple URL that maps to an internal port of your sandbox. You can either make this URL fully public or secure it with token-based authentication.
Generally speaking:
* for file and process management in your sandbox, use the sandbox API or SDK.
* to access running applications within the sandbox *(such as a NextJS preview server on port 3000)*, use a preview URL instead.
### Via SDK
Available in `@blaxel/core` >= 0.2.79 (Node.js) and `blaxel` >= 0.2.49 (Python).
The SDK provides a built-in `fetch` method that handles authentication and proxies requests through the sandbox's `/port/{port}` endpoint.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
// Fetch a resource on port 3000
const response = await sandbox.fetch(3000);
console.log(await response.text());
// Fetch with a specific path
const apiResponse = await sandbox.fetch(3000, "/api/health");
console.log(await apiResponse.json());
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
# Fetch a resource on port 3000
response = await sandbox.fetch(3000)
print(response.text)
# Fetch with a specific path
api_response = await sandbox.fetch(3000, "/api/health")
print(api_response.json())
# Fetch with a custom HTTP method
post_response = await sandbox.fetch(3000, "/api/data", method="POST", json={"key": "value"})
print(post_response.json())
```
For older SDK versions, you can construct the port URL manually. This requires your request to be [authenticated](../Security/Access-tokens).
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
// Connect to existing sandbox
const sandbox = await SandboxInstance.get("my-sandbox")
// Get URL for port access
const url = `${sandbox.metadata.url}/port/3000`
// Use the URL with authenticated requests
const response = await fetch(url, {
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
})
```
```python Python theme={null}
from blaxel.core import SandboxInstance
import requests
# Connect to existing sandbox
sandbox = await SandboxInstance.get("my-sandbox")
# Get URL for port access
url = f"{sandbox.metadata.url}/port/3000"
# Use the URL with authenticated requests
response = requests.get(url, headers={ "Authorization": f"Bearer YOUR_TOKEN" })
```
Expose applications running within the sandbox via a direct preview URL.
# Real-time previews
Source: https://docs.blaxel.ai/Sandboxes/Preview-url
Render an application in real-time through a direct preview URL backed by a running Blaxel sandbox, with optional auth and custom domains.
Sometimes you may need to access a running sandbox application and preview the content in real time in a front-end client. This is useful for example to instantly preview React code generated by a codegen AI agent.
You can do this via a **preview URL** that routes to a specific port on your sandbox (e.g. *port 3000* for `npm run dev -- --host $HOST --port 3000 &`). This preview URL can be either **public** (does not require you to be authenticated to access it) or **private** (see below).
They will look something like this:
```text theme={null}
https://tkmu0oj2bf6iuoag6mmlt8.us-pdx-1.preview.bl.run
```
You can have multiple preview URLs per sandbox.
If you see a 502 error when accessing the preview URL, the most common cause is that your application server is not reachable externally. To resolve this, configure your server to bind to the address provided by the `HOST` environment variable, which Blaxel injects into the sandbox, rather than a static value. If you need a static fallback (for example when running the same code outside a sandbox), use `::` so it listens on all available network interfaces over both IPv4 and IPv6 (use `0.0.0.0` only if you specifically need to restrict the server to IPv4). Note that not every server or dev tool accepts a bare `::` as a host value — confirm your specific tool supports IPv6 wildcard binding before relying on it. Sample server startup commands are shown below:
```tsx TypeScript theme={null}
npm run dev -- --host $HOST
# or
npm serve -- --host $HOST
```
```python Python theme={null}
python main.py --host $HOST
```
Read more about this in our [troubleshooting page](/troubleshooting).
You can also set a **custom domain** on a preview URL (see down below).
## Current limitations of real-time previews
JavaScript module bundlers handle real-time previewing. Here are the key compatibility requirements and limitations:
* Module bundler **must implement** [ping-pong](https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2).
* Both [Webpack](https://webpack.js.org/) and [Turbopack](https://nextjs.org/docs/app/api-reference/turbopack) (v16.1.1) have been tested and confirmed to work.
* Blaxel has a **15-minute connection timeout**. To maintain previews beyond this limit, ensure your bundler implements automatic reconnection.
* You cannot create a preview on **port 80** which is reserved for the system.
Using a Webpack server but unable to hot reload your previews? Check our [troubleshooting page](/troubleshooting) for possible solutions.
## Private preview URLs
When you create a private preview URL a token is required to access the URL. You must include the token as:
* a `bl_preview_token` query parameter when accessing the preview URL (e.g. *[https://tkmu0oj2bf6iuoag6mmlt8.preview.bl.run/health?bl\_preview\_token=\{token.value}](https://tkmu0oj2bf6iuoag6mmlt8.preview.bl.run/health?bl_preview_token=\{token.value})*)
* an `X-Blaxel-Preview-Token` header
## Manage preview URLs
To whitelist sandbox traffic in your network, you can [retrieve the public IP addresses](/Infrastructure/Regions#public-ip-addresses) used by Blaxel.
### Blaxel console
You can create a preview URL for a sandbox from the Blaxel Console, on the overview of a sandbox:
### Blaxel SDK
The Blaxel SDK requires two environment variables to authenticate:
| Variable | Description |
| - | - |
| `BL_WORKSPACE` | Your Blaxel workspace name |
| `BL_API_KEY` | Your Blaxel API key |
You can create an API key from the [Blaxel console](https://app.blaxel.ai/profile/security). Your workspace name is visible in the URL when you log in to the console (e.g. `app.blaxel.ai/{workspace}`).
Set them as environment variables or add them to a `.env` file at the root of your project:
```bash theme={null}
export BL_WORKSPACE=my-workspace
export BL_API_KEY=my-api-key
```
The Blaxel SDK does not accept credentials as constructor arguments. Credentials must come from environment variables, a `.env` file, or a local CLI login session (see below).
When developing locally, you can also **log in to your workspace with Blaxel CLI** (as shown above). This allows you to run Blaxel SDK functions that will automatically connect to your workspace without additional setup. When you deploy on Blaxel, authentication is handled automatically — no environment variables needed.
The recommended pattern for preview creation is `createIfNotExists()` / `create_if_not_exists()`. Blaxel first checks for an existing preview with the provided `name` and either returns it or creates a new one using your specified configuration.
Preview names only need to be unique within a sandbox. Use a constant name such as `"app"` across all your sandboxes to reuse one preview per sandbox. Always provide `metadata.name` when using the SDKs.
Create and manage a sandbox’s public preview URL:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
// Create public preview
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "app-preview" },
spec: {
port: 3000,
public: true,
responseHeaders: {
"Access-Control-Allow-Origin": "https://YOUR-DOMAIN",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
"Vary": "Origin"
}
}
});
// Get preview URL
const url = preview.spec?.url;
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
# Create public preview
preview = await sandbox.previews.create_if_not_exists({
"metadata": {"name": "app-preview"},
"spec": {
"port": 3000,
"public": True,
"responseHeaders": {
"Access-Control-Allow-Origin": "https://YOUR-DOMAIN",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
"Vary": "Origin"
}
}
})
# Get preview URL
url = preview.spec.url
```
To call a preview URL from a frontend on another origin, see [Handle CORS on Blaxel](./CORS).
For a private preview, list its tokens and reuse one that remains valid long enough for your session. Create a new token only when none qualifies. This example requires at least one hour of remaining validity and otherwise creates a token valid for one day. Expired tokens are deleted automatically.
`createIfNotExists` (`create_if_not_exists` in Python) returns existing resources unchanged, so check their region and preview privacy when reusing names.
Run your application on port 3000 before opening the URL.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.createIfNotExists({
name: "my-app",
region: "us-was-1"
});
// Create private preview
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "app" },
spec: {
port: 3000,
public: false
}
});
const token = (await preview.tokens.list()).find(
t => !t.expired && +new Date(t.expiresAt) > Date.now() + 3_600_000
) ?? await preview.tokens.create(new Date(Date.now() + 86_400_000));
const url = `${preview.spec.url}?bl_preview_token=${token.value}`;
const response = await fetch(url);
```
```python Python theme={null}
import httpx
from blaxel.core import SandboxInstance
from datetime import datetime, timedelta, UTC
sandbox = await SandboxInstance.create_if_not_exists({
"name": "my-app",
"region": "us-was-1"
})
# Create private preview
preview = await sandbox.previews.create_if_not_exists({
"metadata": {"name": "app"},
"spec": {
"port": 3000,
"public": False
}
})
min_expiry = datetime.now(UTC) + timedelta(hours=1)
token = next((
t for t in await preview.tokens.list()
if datetime.fromisoformat(t.expires_at.replace("Z", "+00:00")) > min_expiry
), None)
if token is None:
token = await preview.tokens.create(datetime.now(UTC) + timedelta(days=1))
url = f"{preview.spec.url}?bl_preview_token={token.value}"
async with httpx.AsyncClient() as client:
response = await client.get(url)
```
Treat token-bearing URLs as credentials. Share them only with users who should have access to the preview.
An alternative is to use `create()`, which raises an error when a preview with the specified name already exists:
```typescript TypeScript theme={null}
const preview = await sandbox.previews.create({
metadata: {
name: "preview-name"
},
spec: {
port: 443,
public: false
}
})
```
```python Python theme={null}
preview = await sandbox.previews.create({
"metadata": {
"name": "preview-name"
},
"spec": {
"port": 443,
"public": False
}
})
```
### URL prefix
`prefixUrl` (`prefix_url` in Python) is optional. Without it, Blaxel builds a stable URL from your workspace, sandbox, and preview names. The same names in the same region produce the same URL, so you do not need to generate a unique prefix for each sandbox.
Set `prefixUrl` only when you want a custom string prefix:
```typescript TypeScript theme={null}
const sandbox = await SandboxInstance.get("my-sandbox");
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "app-preview" },
spec: {
port: 3000,
public: true,
prefixUrl: "my-prefix"
}
});
```
```python Python theme={null}
sandbox = await SandboxInstance.get("my-sandbox")
preview = await sandbox.previews.create_if_not_exists({
"metadata": {"name": "app-preview"},
"spec": {
"port": 3000,
"public": True,
"prefix_url": "my-prefix"
}
})
```
When using a prefix URL, the workspace name is automatically added to the prefix and included in the final structure for the preview URL - for example, `https://myprefix-workspace-xxx.preview.bl.run`.
## Custom domains
To set up a custom domain for your sandbox preview:
* Register a [custom domain](../Infrastructure/Custom-domains) to your Blaxel workspace and complete the verification process
* Use this verified custom domain when creating a new preview:
```typescript TypeScript {8} theme={null}
const preview = await sandbox.previews.createIfNotExists({
metadata: {
name: "preview-custom-domain"
},
spec: {
port: 443,
public: false,
customDomain: "your.custom.domain.dev"
}
})
```
```python Python {8} theme={null}
preview = await sandbox.previews.create_if_not_exists({
"metadata": {
"name": "preview-custom-domain"
},
"spec": {
"port": 443,
"public": True,
"customDomain": "your.custom.domain.dev"
}
})
```
When you register a custom domain, you also enable the use of wildcard subdomains for that domain. For example, if you register `mycompany.com`, you can configure preview URLs to use any `*.mycompany.com` subdomain.
### Fallback preview
You can also designate one preview as the fallback or catch-all for a custom domain. Any request to a subdomain that does not match an existing preview is routed to this fallback preview instead of returning a 404.
Common use cases for this include:
* Serving a branded "page not found" or landing page instead of a raw 404
* Routing all traffic to a primary preview by default
* Letting your application handle unknown subdomains with custom logic
To use a fallback preview, you must have:
* A [verified custom domain](#custom-domains) in your workspace
* At least one active preview on that custom domain
To configure a fallback preview from the console:
1. Go to **Workspace Settings** > **Custom Domains**
2. Click the **...** menu on the domain you want to configure
3. Select **Set Fallback Preview**
4. Enter the name of the preview to use as the catch-all
5. Click **Save**
To remove the fallback, open the same dialog and clear the preview name.
To configure from the API, update the `fallbackPreviewId` field on the custom domain resource:
```bash Set fallback theme={null}
curl -X PUT "https://api.blaxel.ai/v0/customdomains/{domainName}" \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: $BL_WORKSPACE" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"name": "yourdomain.com"
},
"spec": {
"fallbackPreviewId": "my-landing-page"
}
}'
```
```bash Remove fallback theme={null}
curl -X PUT "https://api.blaxel.ai/v0/customdomains/{domainName}" \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: $BL_WORKSPACE" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"name": "yourdomain.com"
},
"spec": {
"fallbackPreviewId": ""
}
}'
```
When a request arrives for a subdomain on your custom domain:
1. Blaxel checks for a preview matching that subdomain.
2. If no match is found and a fallback is configured, the request is routed to the fallback preview.
3. If no fallback is configured, the request returns a 404.
The fallback only applies to requests on your custom domain. Standard Blaxel preview URLs (`*.preview.bl.run`) are not affected.
When a request is routed via fallback, Blaxel adds headers to the request so your application can customize its response:
| Header | Value | Description |
| - | - | - |
| `X-Blaxel-Fallback` | `true` | Indicates the request was routed via fallback |
| `X-Blaxel-Fallback-Preview` | Preview name | The subdomain or preview name originally requested |
| `X-Blaxel-Fallback-Domain` | Domain name | The base custom domain (e.g. `yourdomain.com`) |
Use `X-Blaxel-Fallback-Preview` to display a contextual message such as "Preview \[name] was not found" on your fallback page.
Security considerations:
* The fallback preview must belong to the same workspace as the custom domain; cross-workspace fallbacks are not allowed.
* Client-injected `X-Blaxel-Fallback-*` headers are stripped before routing, so only Blaxel-set values are forwarded.
* If the fallback lookup fails for any reason, the request falls through to normal routing without blocking traffic.
For example, suppose you have a custom domain `demo-previews.store` with two previews:
| Preview name | URL |
| - | - |
| `my-app` | `my-app.demo-previews.store` |
| `landing-page` | `landing-page.demo-previews.store` |
With `landing-page` set as the fallback, requests are routed as follows:
* `my-app.demo-previews.store` routes to the `my-app` preview (direct match)
* `landing-page.demo-previews.store` routes to the `landing-page` preview (direct match)
* `unknown.demo-previews.store` routes to the `landing-page` preview (fallback)
On fallback requests, your `landing-page` application receives `X-Blaxel-Fallback: true` and `X-Blaxel-Fallback-Preview: unknown`, allowing it to show a contextual error page.
## Delete a preview
When a sandbox is deleted, whether manually or automatically due to a TTL or expiration policy, all of its associated preview URLs are automatically cleaned up as part of that deletion.
If you need to remove a preview URL before a sandbox is deleted, you can do so explicitly using the SDKs.
```typescript TypeScript theme={null}
const sandbox = await SandboxInstance.get("my-sandbox");
await sandbox.previews.delete("app-preview");
```
```python Python theme={null}
sandbox = await SandboxInstance.get("my-sandbox")
await sandbox.previews.delete("app-preview")
```
## FAQ
**What is a fallback preview for a custom domain?**
A fallback preview is a designated preview that receives requests for unrecognized subdomains on your custom domain, instead of returning a 404 error.
**When does Blaxel route traffic to the fallback preview?**
Blaxel routes to the fallback when a request arrives for a subdomain that does not match any existing preview on that custom domain. If no fallback is configured, the request returns a 404.
**How do I configure a fallback preview from the console?**
In the Blaxel Console, go to **Workspace Settings** > **Custom Domains**, click the **...** menu on the domain, select **Set Fallback Preview**, enter the preview name, and click **Save**.
**What headers does my application receive on a fallback request?**
Blaxel adds `X-Blaxel-Fallback: true`, `X-Blaxel-Fallback-Preview` (the subdomain that was requested), and `X-Blaxel-Fallback-Domain` (the base custom domain) to all fallback requests.
**How do I remove a fallback preview?**
From the console, open the Configure Fallback dialog and clear the preview name. From the API, send a PUT request with `fallbackPreviewId` set to an empty string.
Register and verify a custom domain for your Blaxel workspace.
Configure TTL and auto-deletion rules for sandboxes.
# Process execution
Source: https://docs.blaxel.ai/Sandboxes/Processes
Execute shell commands, retrieve process information, stream output, and control process lifecycle in Blaxel sandboxes using the SDK.
Execute and manage processes in your sandboxes with Blaxel SDK. Run shell commands, retrieve process information, and control process execution.
Complete code examples demonstrating all operations are available on Blaxel's GitHub: [in TypeScript](https://github.com/blaxel-ai/sdk-typescript/tree/main/tests/sandbox), [in Python](https://github.com/blaxel-ai/sdk-python/tree/main/tests/integration/sandbox), and [in Go](https://github.com/blaxel-ai/sdk-go/tree/main/integration_tests).
## Execute processes and commands
The Blaxel SDK requires two environment variables to authenticate:
| Variable | Description |
| - | - |
| `BL_WORKSPACE` | Your Blaxel workspace name |
| `BL_API_KEY` | Your Blaxel API key |
You can create an API key from the [Blaxel console](https://app.blaxel.ai/profile/security). Your workspace name is visible in the URL when you log in to the console (e.g. `app.blaxel.ai/{workspace}`).
Set them as environment variables or add them to a `.env` file at the root of your project:
```bash theme={null}
export BL_WORKSPACE=my-workspace
export BL_API_KEY=my-api-key
```
The Blaxel SDK does not accept credentials as constructor arguments. Credentials must come from environment variables, a `.env` file, or a local CLI login session (see below).
When developing locally, you can also **log in to your workspace with Blaxel CLI** (as shown above). This allows you to run Blaxel SDK functions that will automatically connect to your workspace without additional setup. When you deploy on Blaxel, authentication is handled automatically — no environment variables needed.
### Execute command
Execute shell commands:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
// Execute command
const process = await sandbox.process.exec({
command: "echo 'Hello, World!'"
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
# Execute command
process = await sandbox.process.exec({
"command": "echo 'Hello, World!'"
})
```
By default, `exec` returns after starting the process without waiting for it to finish. Its output may not be available yet.
### Get command output
Set `waitForCompletion: true` (`"wait_for_completion": True` in Python) to wait for the command to finish. The result then holds its `stdout`, `stderr`, and `exitCode` (`exit_code` in Python):
```typescript TypeScript theme={null}
const result = await sandbox.process.exec({
command: "ls /",
waitForCompletion: true
});
console.log(result.exitCode);
console.log(result.stdout);
console.log(result.stderr);
```
```python Python theme={null}
result = await sandbox.process.exec({
"command": "ls /",
"wait_for_completion": True
})
print(result.exit_code)
print(result.stdout)
print(result.stderr)
```
The `logs` field holds `stdout` and `stderr` together. A command that exits with a non-zero code does not raise an error: its `status` is `failed`, so check the exit code. Set `timeout` to bound how long the call waits. For longer commands, see [Wait for process completion](#wait-for-process-completion).
Process execution logs are also visible in the Blaxel Console. Refer to the **Logs** section of the sandbox detail page, as shown below:
### Set the working directory
Set the working directory for subsequent commands:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
// Execute command
const process = await sandbox.process.exec({
workingDir: "/app",
command: "ls -al"
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
# Execute command
process = await sandbox.process.exec({
"working_dir": "/app",
"command": "ls -al"
})
```
`workingDir` is a JSON request-body field, not a URL path. Pass a normal absolute filesystem path such as `"/workspace"`; do not URL-encode it as `"%2Fworkspace"`. In Python, the equivalent field is `working_dir`.
### Use process names
When starting a process (running a command), you can specify a **process name**. This lets you interact with the process—such as retrieving logs or process information—without needing to store the process ID on your end.
```typescript TypeScript highlight={2} theme={null}
const process = await sandbox.process.exec({
name: "hello-process",
command: "echo 'Hello, World!'"
});
const processInfo = await sandbox.process.get("hello-process");
```
```python Python highlight={2} theme={null}
process = await sandbox.process.exec({
"name": "hello-process",
"command": "echo 'Hello, World!'"
})
process_info = await sandbox.process.get("hello-process")
```
You can use either the process name or the process ID to get information about the process:
```typescript TypeScript theme={null}
const completedProcess = await sandbox.process.get("hello-process");
if (completedProcess.status === "completed") {
// ...
}
```
```python Python theme={null}
completed_process = await sandbox.process.get("hello-process")
if completed_process.status == "completed":
# ...
```
The exit code of the process is available in the `exitCode` parameter (`exit_code` in Python) of the process execution object returned by the `process.exec` and `process.get` methods.
You can also use the process ID or name to [retrieve logs of your processes](/Sandboxes/Log-streaming).
### Kill process
Kill a process immediately by running:
```typescript TypeScript theme={null}
await sandbox.process.kill("test")
```
```python Python theme={null}
await sandbox.process.kill("test")
```
## Long-running processes
Blaxel Sandboxes are great for long-running processes. Processes you launch detach by design: their `stdout` and `stderr` are redirected to files and the process is monitored by background workers in the sandbox, so it survives independently of your client connection.
By default, the general [sandbox lifecycle](/Sandboxes/Overview#sandbox-lifecycle) rules still apply: the sandbox only stays awake while a connection is maintained and will suspend after a few seconds of inactivity. Use `keepAlive` to override this for the duration of a process.
Two independent flags let you tune how the behavior works.
* `waitForCompletion` (default: `false`) controls only whether the SDK call blocks until the process exits and returns its output. It has no effect on the process or sandbox lifetime.
* `keepAlive` (default: none) controls only whether the sandbox stays awake (auto-standby/scale-to-zero disabled) while the process runs, even with no active client connection. A counter tracks running `keepAlive` processes and is decremented automatically when each one completes, letting the sandbox return to standby on its own. See [Sandbox keep-alive](#sandbox-keep-alive) for details.
### Wait for process completion
You can wait for process completion when executing it:
```typescript TypeScript theme={null}
const process = await sandbox.process.exec({
name: "build-process",
command: "npm run build",
waitForCompletion: true,
timeout: 60 // 60 seconds
});
```
```python Python theme={null}
process = await sandbox.process.exec({
"name": "build-process",
"command": "npm run build",
"wait_for_completion": True,
"timeout": 60 # 60 seconds
})
```
When waiting for completion, the process execution object contains the command's [output](#get-command-output): `stdout`, `stderr`, and both combined in `logs`. Also, notice the `timeout` parameter which allows you to set the process timeout duration in seconds.
When using `waitForCompletion`, Blaxel enforces a **timeout limit of 60 seconds**. Don't set your timeout longer than this. For longer waiting periods, use the process-watching option described below.
For processes **without** `keepAlive: true`, the `timeout` parameter does **not** kill the process. It only bounds how long the API call waits when using `waitForCompletion` (or `waitForPorts`); once the timeout elapses, an error is returned but the process keeps running in the background. Non-`keepAlive` processes have no auto-kill timeout and run until they complete on their own. To auto-kill a process after a duration, use `keepAlive: true` together with a `timeout` (see [Sandbox keep-alive](#sandbox-keep-alive)).
You can also wait for a process after it has started:
```typescript TypeScript theme={null}
await sandbox.process.exec({
name: "long-task",
command: "sleep 10"
});
// Wait for completion (max 10 minutes, check every 5 seconds)
await sandbox.process.wait("long-task", {
maxWait: 600000,
interval: 5000
});
```
```python Python theme={null}
await sandbox.process.exec({
"name": "long-task",
"command": "sleep 10"
})
# Wait for completion (max 10 minutes, check every 5 seconds)
await sandbox.process.wait("long-task", max_wait=600000, interval=5000)
```
Set a long completion duration if your process is expected to take longer.
### Wait for ports
In some cases, you may want to wait for a port to be opened while running — for example if you are running `npm run dev` and want to wait for port 3000 to be open. `exec` returns as soon as the ports are open, and the process keeps running in the background.
```typescript TypeScript theme={null}
const process = await sandbox.process.exec({
name: "dev-server",
command: "npm run dev -- --host $HOST --port 3000",
waitForPorts: [3000],
timeout: 60
});
```
```python Python theme={null}
process = await sandbox.process.exec({
"name": "dev-server",
"command": "npm run dev -- --host $HOST --port 3000",
"wait_for_ports": [3000],
"timeout": 60
})
```
A port counts as open once the server listens on the address in `HOST` (or on all interfaces). A server bound only to `localhost` isn't reachable from outside the sandbox, so it doesn't count.
`timeout` bounds how long the call waits for the ports. Keep it under 120 seconds: a request that stays open that long is cut off by the gateway with a 504 error instead of the timeout error. If the process fails before its ports open (it exits with a non-zero code or is killed), the call fails right away instead of waiting out the timeout. The error includes the exit code and the last lines of the process's output:
```typescript TypeScript theme={null}
try {
await sandbox.process.exec({
name: "dev-server",
command: "npm run dev -- --host $HOST --port 3000",
waitForPorts: [3000],
timeout: 60
});
} catch (error) {
// e.g. "process exited with code 1 before ports [3000] opened; last output: ..."
console.error(error.message);
}
```
```python Python theme={null}
try:
await sandbox.process.exec({
"name": "dev-server",
"command": "npm run dev -- --host $HOST --port 3000",
"wait_for_ports": [3000],
"timeout": 60
})
except Exception as error:
# e.g. "process exited with code 1 before ports [3000] opened; last output: ..."
print(error)
```
Don't end the command with `&`. The shell exits straight away and the server it put in the background isn't tracked as part of the process, so the call waits until `timeout` and a startup crash isn't reported. Failing fast needs sandbox-api 0.2.62 or later; custom images pick it up when rebuilt.
## Process stdin
By default, a process has no stdin: it reads end-of-file as soon as it starts. Start it with `stdin: true` to give it a writable pipe, then send input with `writeStdin` and close the pipe with `closeStdin`. Use this to drive programs that read from stdin, such as stdio MCP servers and other JSON-RPC programs.
The pipe passes your bytes through unchanged. Unlike the [interactive terminal](/Sandboxes/Overview#connect-to-a-sandbox-with-an-interactive-terminal), there is no TTY in between, so nothing is echoed back or rewritten.
Process stdin needs `@blaxel/core` 0.3.19 or later in TypeScript, `blaxel` 0.4.9 or later in Python, and sandbox-api 0.2.56 or later in the sandbox. Custom images pick it up when rebuilt.
### Write to a process
Start the process with a name and `stdin: true`, write to it, then close stdin to send end-of-file:
```typescript TypeScript theme={null}
await sandbox.process.exec({
name: "sort",
command: "sort",
stdin: true
});
await sandbox.process.writeStdin("sort", "cherry\napple\nbanana\n");
await sandbox.process.closeStdin("sort");
await sandbox.process.wait("sort");
const output = await sandbox.process.logs("sort", "stdout");
// "apple\nbanana\ncherry\n"
```
```python Python theme={null}
await sandbox.process.exec({
"name": "sort",
"command": "sort",
"stdin": True
})
await sandbox.process.write_stdin("sort", "cherry\napple\nbanana\n")
await sandbox.process.close_stdin("sort")
await sandbox.process.wait("sort")
output = await sandbox.process.logs("sort", "stdout")
# "apple\nbanana\ncherry\n"
```
`writeStdin` (`write_stdin` in Python) takes a string or bytes and writes them as is, so include the trailing newline your program expects. `closeStdin` (`close_stdin` in Python) can be called more than once.
### Run a stdio MCP server
A stdio MCP server reads JSON-RPC messages from stdin and writes one reply per line to stdout. Send each message with `writeStdin`, and read the replies by [streaming the process's stdout](/Sandboxes/Log-streaming):
```typescript TypeScript theme={null}
await sandbox.process.exec({
name: "mcp-fs",
command: "npx -y @modelcontextprotocol/server-filesystem /tmp",
stdin: true
});
// Each stdout line is one JSON-RPC message from the server
const pending = new Map void>();
const stream = sandbox.process.streamLogs("mcp-fs", {
onStdout: (line) => {
const message = JSON.parse(line);
pending.get(message.id)?.(message);
}
});
const send = (message: object) =>
sandbox.process.writeStdin("mcp-fs", JSON.stringify(message) + "\n");
const request = (id: number, method: string, params: object = {}) =>
new Promise((resolve) => {
pending.set(id, resolve);
void send({ jsonrpc: "2.0", id, method, params });
});
await request(1, "initialize", {
protocolVersion: "2025-06-18",
capabilities: {},
clientInfo: { name: "my-client", version: "1.0.0" }
});
await send({ jsonrpc: "2.0", method: "notifications/initialized" });
const tools = await request(2, "tools/list");
// Closing stdin shuts the server down
await sandbox.process.closeStdin("mcp-fs");
stream.close();
```
```python Python theme={null}
import asyncio
import json
await sandbox.process.exec({
"name": "mcp-fs",
"command": "npx -y @modelcontextprotocol/server-filesystem /tmp",
"stdin": True
})
# Each stdout line is one JSON-RPC message from the server
pending = {}
def on_stdout(line):
message = json.loads(line)
reply = pending.pop(message.get("id"), None)
if reply:
reply.set_result(message)
stream = sandbox.process.stream_logs("mcp-fs", {"on_stdout": on_stdout})
async def send(message):
await sandbox.process.write_stdin("mcp-fs", json.dumps(message) + "\n")
async def request(request_id, method, params=None):
reply = asyncio.get_running_loop().create_future()
pending[request_id] = reply
await send({"jsonrpc": "2.0", "id": request_id, "method": method, "params": params or {}})
return await reply
await request(1, "initialize", {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {"name": "my-client", "version": "1.0.0"}
})
await send({"jsonrpc": "2.0", "method": "notifications/initialized"})
tools = await request(2, "tools/list")
# Closing stdin shuts the server down
await sandbox.process.close_stdin("mcp-fs")
stream.close()
```
The first `npx` run downloads the server package, so the first reply can take several seconds.
### Stdin errors and restarts
Sequential writes keep their order, and two concurrent writes never mix inside a message. The SDK does not retry a failed write, since a duplicate would corrupt the stream. A write fails with:
* `409` if the process was started without `stdin: true`, or its stdin is closed
* `413` if the data is larger than 8 MiB
* `503` if the process stopped reading and the pipe stayed full for 10 seconds
After a `503`, part of the data may have gone through, so restart the process.
The pipe survives sandbox standby and resume. With [`restartOnFailure`](#restart-process-on-failure), each restart gets a new pipe, and data written between the failure and the restart is lost. If the sandbox API itself restarts, the process reads end-of-file and writes fail with `409`: restart the process and redo your protocol's handshake.
Over the Sandbox API, start the process with `"stdin": true` on `POST /process`, then [write to its stdin](/api-reference/process/write-to-a-processs-stdin) with `POST /process/{identifier}/stdin` and the raw bytes as the request body, and [close its stdin](/api-reference/process/close-a-processs-stdin) with `DELETE /process/{identifier}/stdin`.
## Auto-restart
### Restart process on failure
You can restart a process if it fails, up to a maximum number of restart attempts:
```typescript TypeScript theme={null}
const process = await sandbox.process.exec({
name: "start-server",
command: "npm run dev -- --host $HOST --port 8000",
restartOnFailure: true,
maxRestarts: 5
});
```
```python Python theme={null}
process = await sandbox.process.exec({
"name": "start-server",
"command": "uvicorn main:app --reload --host $HOST --port 8000",
"restart_on_failure": True,
"max_restarts": 5
})
```
## Sandbox keep-alive
By default, sandboxes automatically switch to standby when there is no active connection to save resources. However, it is possible to adjust this behavior and keep the sandbox running when you launch a process, even if there isn't an active connection to it. This is typically used to keep a long-running task actively running within the sandbox environment even when it is not serving requests.
You can include additional keep-alive metadata when launching a new process:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
// Start a dev server that keeps the sandbox awake
await sandbox.process.exec({
name: "dev-server",
command: "npm run dev -- --hostname $HOST --port 3000",
keepAlive: true, // Prevents auto-standby while the process runs
timeout: 3600 // Auto-kill the process after 1 hour. Set 0 to not auto-kill.
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
# Start a dev server that keeps the sandbox awake
await sandbox.process.exec({
"name": "dev-server",
"command": "npm run dev -- --hostname $HOST --port 3000",
"keep_alive": True, # Prevents auto-standby
"timeout": 3600 # Auto-kill after 1 hour
})
```
The auto-kill timeout and its 600-second default **only apply to processes started with `keepAlive: true`**. If a `keepAlive` process is started without a `timeout`, it defaults to **600 seconds (10 minutes)** and is automatically terminated when the timeout elapses. To run a `keepAlive` process indefinitely with no auto-kill, set `timeout: 0`.
The table below summarizes how `keepAlive` and `timeout` interact:
| Configuration | Process auto-kill behavior | `timeout` parameter effect |
| - | - | - |
| `keepAlive` not set (default) | Never auto-killed. Runs until it completes on its own. | Only bounds how long the API waits when using `waitForCompletion` or `waitForPorts`. Does not terminate the process. |
| `keepAlive: true`, no `timeout` | Auto-killed after **600 seconds** (10 minutes). | — |
| `keepAlive: true`, `timeout: 0` | Never auto-killed. Runs until it completes on its own. | — |
| `keepAlive: true`, `timeout: N` | Auto-killed after **N seconds**. | — |
You can run multiple processes, and the sandbox will stay active until all of them complete:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
await sandbox.process.exec({
name: "api-server",
command: "npm run start:api",
keepAlive: true,
timeout: 3600
});
await sandbox.process.exec({
name: "worker",
command: "npm run start:worker",
keepAlive: true,
timeout: 3600
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
await sandbox.process.exec({
"name": "api-server",
"command": "npm run start:api",
"keep_alive": True,
"timeout": 3600
})
await sandbox.process.exec({
"name": "worker",
"command": "npm run start:worker",
"keep_alive": True,
"timeout": 3600
})
```
## Disable process telemetry logging
By default, process stdout and stderr are exported as structured telemetry logs (visible in external observability systems). You can disable this telemetry export by setting the `SANDBOX_DISABLE_PROCESS_LOGGING` environment variable inside the sandbox.
When set to `true` or `1`, structured telemetry output is suppressed for **all** processes. File-based logging and [real-time log streaming](/Sandboxes/Log-streaming) via the SDK remain fully functional and are not affected.
Set this variable in your Dockerfile or at sandbox creation time:
```dockerfile theme={null}
ENV SANDBOX_DISABLE_PROCESS_LOGGING=true
```
Alternatively, you can disable process logging for sandboxes at workspace level using the Blaxel Console, in the **Workspace settings** section.
This only suppresses the structured telemetry export. You can still retrieve process logs via the SDK's `process.logs()` method and stream them in real-time.
## Process statuses
A process can have any of the following statuses:
* `running`
* `completed`
* `failed`
* `killed`
* `stopped`
# Proxy
Source: https://docs.blaxel.ai/Sandboxes/Proxy
Route outbound sandbox traffic through Blaxel's proxy with MITM header and body injection, inject secrets, and configure per-domain firewalls.
This feature is currently in public preview and is not recommended for production use.
The Blaxel SDK supports two network features when creating sandboxes:
* **[Proxy routing with secrets injection](./Proxy-secrets-injection)** - the Blaxel proxy performs man-in-the-middle (MITM) interception on outbound HTTPS traffic and injects headers, body fields, and secrets server-side.
* **[Domain filtering](./Proxy-domains)** - the Blaxel proxy controls which external domains the sandbox can reach.
Proxy and network configuration can be updated after creation: you can rotate secrets, add or remove routing rules, and change the domain allowlist or denylist on a running sandbox.
You cannot enable the proxy on a sandbox that was created without it, or fully disable the proxy on a sandbox that was created with it. Enabling or disabling the proxy requires a new sandbox. If you remove all routing rules and domain filters from an existing proxy-enabled sandbox, the proxy remains active transparently — traffic continues to flow through it without any secret injection or domain blocking.
## How it works
When a sandbox is created with a proxy config, Blaxel:
1. Sets `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables inside the sandbox
2. Installs a CA certificate and sets `NODE_EXTRA_CA_CERTS` and `SSL_CERT_FILE` so TLS clients trust the proxy
3. Performs MITM on outbound HTTPS via CONNECT tunneling
4. Matches each request against routing rules by destination domain
5. Injects configured headers and body fields, resolving `{{SECRET:name}}` placeholders server-side
6. Adds an `X-Blaxel-Request-Id` header to every proxied request for tracing
Most HTTP clients work transparently because they read the injected `HTTP_PROXY`, `HTTPS_PROXY`, and `SSL_CERT_FILE` environment variables — `curl`, `pip`, `npm`, `git`, Python `requests`, and Python `httpx` all work out of the box. Lower-level libraries that don't follow these conventions (for example, calling `urllib3` directly) need to be pointed at the proxy explicitly.
Localhost (`127.0.0.1`), private ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), `169.254.169.254`, `.local`, and `.internal` are always bypassed automatically.
## Configuration reference
All network settings are passed via the `network` key in the sandbox creation options:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.create({
name: "my-sandbox",
image: "blaxel/base-image:latest",
region: "us-was-1",
network: {
// Domain filtering (firewall)
allowedDomains: [...],
forbiddenDomains: [...],
// Proxy with header/body injection
proxy: {
routing: [...],
bypass: [...],
},
},
});
```
```python Python theme={null}
from blaxel.core.sandbox import SandboxInstance
sandbox = await SandboxInstance.create({
"name": "my-sandbox",
"image": "blaxel/base-image:latest",
"region": "us-was-1",
"network": {
# Domain filtering (firewall)
"allowedDomains": [...],
"forbiddenDomains": [...],
# Proxy with header/body injection
"proxy": {
"routing": [...],
"bypass": [...],
},
},
})
```
Adding a proxy config to a sandbox enforces the proxy on **all** outbound requests. If some requests should not go through it, use [proxy bypass](./Proxy-secrets-injection#proxy-bypass).
### `SandboxNetwork`
| Field | Type | Description |
| - | - | - |
| `allowedDomains` | `string[]` / `list[str]` | Allowlist — only these domains are reachable. Supports wildcards (`*.s3.amazonaws.com`). |
| `forbiddenDomains` | `string[]` / `list[str]` | Denylist — all domains except these are reachable. Supports wildcards. If a request matches both lists, `forbiddenDomains` takes precedence and the request is blocked. |
| `firewall` | `FirewallConfig` | Network-level lockdown that forces sandbox egress through the proxy. See [domain filter enforcement](./Proxy-domains#domain-filter-enforcement). |
| `proxy` | `ProxyConfig` | Proxy routing and bypass configuration. |
### `FirewallConfig`
| Field | Type | Description |
| - | - | - |
| `rulesets` | `string[]` / `list[str]` | Lockdown rulesets to apply. Set to `["proxy"]` to force all sandbox outbound traffic through the Blaxel proxy, even for tools that ignore `HTTP_PROXY`/`HTTPS_PROXY`. Can only be set at sandbox creation. |
### `ProxyConfig`
| Field | Type | Description |
| - | - | - |
| `routing` | `ProxyTarget[]` / `list[ProxyTarget]` | Per-destination routing rules with header/body injection. |
| `bypass` | `string[]` / `list[str]` | Domains added to `NO_PROXY` that skip the proxy entirely. Supports wildcards. |
### `ProxyTarget`
| Field | Type | Description |
| - | - | - |
| `destinations` | `string[]` / `list[str]` | Domain patterns this rule applies to. Use `["*"]` for a global catch-all rule. Supports wildcards (`*.example.com` matches `sub.example.com` but not `example.com`). |
| `headers` | `Record` / `dict` | Headers injected into matching requests. Values may contain `{{SECRET:name}}` references. |
| `body` | `Record` / `dict` | JSON body fields injected into matching requests. Values may contain `{{SECRET:name}}` references. |
| `secrets` | `Record` / `dict` | Named secret values for this rule. Referenced via `{{SECRET:name}}` in headers and body. Write-only — never returned in API responses. Stored encrypted at rest. |
## Region availability
Proxy availability is region-dependent. The `Region` type includes a `proxyAvailable` boolean field. Check region support before relying on proxy features:
```typescript TypeScript theme={null}
import { listRegions } from "@blaxel/core";
const { data: regions } = await listRegions({ throwOnError: true });
for (const r of regions) {
console.log(`${r.name}: proxy=${r.proxyAvailable}`);
}
```
```python Python theme={null}
from blaxel.core import listRegions
result = await listRegions()
for r in result.data:
print(f"{r.name}: proxy={r.proxy_available}")
```
## Environment variables set inside the sandbox
When proxy is configured, the sandbox automatically has:
| Variable | Purpose |
| - | - |
| `HTTP_PROXY` | Proxy URL for HTTP traffic |
| `HTTPS_PROXY` | Proxy URL for HTTPS traffic |
| `NO_PROXY` | Comma-separated bypass list (always includes localhost, private ranges) |
| `NODE_EXTRA_CA_CERTS` | Path to CA cert for Node.js TLS verification |
| `SSL_CERT_FILE` | Path to CA cert for other TLS clients (`curl`, Python, etc.) |
## CLI tool compatibility
When proxy is enabled, common CLI tools and high-level clients work transparently inside the sandbox with no extra configuration:
| Tool | Protocol | Notes |
| - | - | - |
| `curl` | HTTPS | Automatic via `HTTPS_PROXY` env var |
| `git` | HTTPS | May need `GIT_SSL_CAINFO=$SSL_CERT_FILE` for some operations |
| `pip` / `pip3` | HTTPS | Automatic |
| `npm` / `npx` | HTTPS | Automatic |
| Python `requests` | HTTPS | Automatic via env vars |
| Python `httpx` | HTTPS | Automatic via env vars |
Python's stdlib `urllib.request` is not recommended with the proxy. Prefer `requests` or `httpx`, or configure `urllib3.ProxyManager` directly if you need lower-level control:
```python theme={null}
import os
import urllib.parse
import urllib3
proxy_url = os.environ["HTTPS_PROXY"]
_parsed = urllib.parse.urlsplit(proxy_url)
_userinfo = _parsed.netloc.rsplit("@", 1)
proxy_kwargs = {}
if len(_userinfo) == 2:
proxy_auth = urllib.parse.unquote(_userinfo[0])
proxy_kwargs["proxy_headers"] = urllib3.make_headers(proxy_basic_auth=proxy_auth)
http = urllib3.ProxyManager(
proxy_url,
**proxy_kwargs,
cert_reqs="CERT_REQUIRED",
ca_certs=os.environ.get("SSL_CERT_FILE"),
)
resp = http.request(
"GET",
"https://api.stripe.com/v1/charges"
)
print(resp.status, resp.data.decode())
```
## Behavior details
* **Wildcard matching**: `*.example.com` matches `sub.example.com` and `a.b.example.com` but not `example.com` itself
* **No cross-route leakage**: Headers/secrets from one routing rule are never applied to requests matching a different rule
* **User headers preserved**: The proxy adds injected headers alongside any headers the sandbox code sends — it does not overwrite user-sent headers
* **Body merge**: Injected body fields are merged into JSON request bodies up to 1 MB. User-sent fields take precedence on key collisions. Requests over 1 MB, requests without a known `Content-Length` (e.g. chunked uploads), and streaming RPCs are forwarded unchanged — use header injection for credentials in those cases
* **Tracing**: Every proxied request gets an `X-Blaxel-Request-Id` header for observability
* **Local traffic**: Requests to `localhost` / `127.0.0.1` are never routed through the proxy
### HTTP/2 and gRPC
The proxy supports HTTP/2 and gRPC. Clients that previously required HTTP/1.1 workarounds now work without any flags or downgrades:
* gRPC SDKs in Go, Python (`grpcio`), and Node (`@grpc/grpc-js`)
* `curl --http2`
* Go `net/http` (HTTP/2 is the default)
All four gRPC RPC styles — unary, server-streaming, client-streaming, and bidirectional — flow through, and gRPC trailers (`grpc-status`, `grpc-message`) are preserved end-to-end.
A few things to keep in mind:
* **Header injection works on every request**, including streaming RPCs.
* **Body injection is skipped on streaming requests** (gRPC streams, chunked uploads). Use header injection to attach credentials on streaming workloads — see the body injection limits in [Behavior details](#behavior-details).
## Update proxy configuration
You can update proxy routing rules, secrets, domain filters, and bypass lists on a running sandbox using the SDK's `updateNetwork` method. This fetches the current sandbox, merges the new network config, and applies the update in one call:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
await SandboxInstance.updateNetwork("my-sandbox", {
network: {
allowedDomains: ["api.stripe.com", "api.openai.com", "api.anthropic.com"],
proxy: {
routing: [
{
destinations: ["api.stripe.com"],
headers: { "Authorization": "Bearer {{SECRET:stripe-key}}" },
secrets: { "stripe-key": "sk_live_REPLACE_WITH_YOUR_KEY" },
},
{
destinations: ["api.anthropic.com"],
headers: { "x-api-key": "{{SECRET:anthropic-key}}" },
secrets: { "anthropic-key": "REPLACE_WITH_YOUR_ANTHROPIC_KEY" },
},
],
},
},
});
```
```python Python theme={null}
from blaxel.core.sandbox import SandboxInstance
from blaxel.core.sandbox.types import SandboxUpdateNetwork
await SandboxInstance.update_network(
"my-sandbox",
SandboxUpdateNetwork(network={
"allowedDomains": ["api.stripe.com", "api.openai.com", "api.anthropic.com"],
"proxy": {
"routing": [
{
"destinations": ["api.stripe.com"],
"headers": {"Authorization": "Bearer {{SECRET:stripe-key}}"},
"secrets": {"stripe-key": "sk_live_REPLACE_WITH_YOUR_KEY"},
},
{
"destinations": ["api.anthropic.com"],
"headers": {"x-api-key": "{{SECRET:anthropic-key}}"},
"secrets": {"anthropic-key": "REPLACE_WITH_YOUR_ANTHROPIC_KEY"},
},
],
},
}),
)
```
```bash cURL theme={null}
curl -X PUT "https://api.blaxel.ai/v0/sandboxes/my-sandbox" \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d '{
"metadata": { "name": "my-sandbox" },
"spec": {
"network": {
"allowedDomains": ["api.stripe.com", "api.openai.com", "api.anthropic.com"],
"proxy": {
"routing": [
{
"destinations": ["api.stripe.com"],
"headers": { "Authorization": "Bearer {{SECRET:stripe-key}}" },
"secrets": { "stripe-key": "sk_live_REPLACE_WITH_YOUR_KEY" }
},
{
"destinations": ["api.anthropic.com"],
"headers": { "x-api-key": "{{SECRET:anthropic-key}}" },
"secrets": { "anthropic-key": "REPLACE_WITH_YOUR_ANTHROPIC_KEY" }
}
]
}
}
}
}'
```
The update replaces the entire `network` configuration. Include all routing rules, domain filters, and secrets you want active after the update.
You cannot enable the proxy on a sandbox that was created without it, or fully disable the proxy on a sandbox that was created with it. Removing all routing rules and domain filters does not disable the proxy — traffic continues to flow through it transparently without any injection or blocking.
## Full example: agent sandbox with proxy + firewall
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.create({
name: "agent-workspace",
image: "blaxel/base-image:latest",
region: "us-was-1",
labels: { team: "ml", env: "staging" },
network: {
allowedDomains: [
"api.stripe.com",
"api.openai.com",
"httpbin.org",
"*.s3.amazonaws.com",
],
proxy: {
routing: [
{
destinations: ["api.stripe.com"],
headers: {
"Authorization": "Bearer {{SECRET:stripe-key}}",
"Stripe-Version": "2024-12-18.acacia",
},
body: {
"api_key": "{{SECRET:stripe-key}}",
},
secrets: {
"stripe-key": "sk-live-abc123...",
},
},
{
destinations: ["api.openai.com"],
headers: {
"Authorization": "Bearer {{SECRET:openai-key}}",
"OpenAI-Organization": "org-abc123",
},
secrets: {
"openai-key": "sk-proj-xyz789...",
},
},
],
bypass: ["*.s3.amazonaws.com"],
},
},
});
// curl https://api.stripe.com/... -> gets auth header + body injected
// curl https://api.openai.com/... -> gets auth header injected
// curl https://httpbin.org/... -> allowed, no injection
// curl https://evil.com/... -> BLOCKED by allowedDomains firewall
const result = await sandbox.process.exec({
command: "curl -s https://api.stripe.com/v1/charges",
waitForCompletion: true,
});
console.log(result.logs);
```
```python Python theme={null}
from blaxel.core.sandbox import SandboxInstance
sandbox = await SandboxInstance.create({
"name": "agent-workspace",
"image": "blaxel/base-image:latest",
"region": "us-was-1",
"labels": {"team": "ml", "env": "staging"},
"network": {
"allowedDomains": [
"api.stripe.com",
"api.openai.com",
"httpbin.org",
"*.s3.amazonaws.com",
],
"proxy": {
"routing": [
{
"destinations": ["api.stripe.com"],
"headers": {
"Authorization": "Bearer {{SECRET:stripe-key}}",
"Stripe-Version": "2024-12-18.acacia",
},
"body": {
"api_key": "{{SECRET:stripe-key}}",
},
"secrets": {
"stripe-key": "sk-live-abc123...",
},
},
{
"destinations": ["api.openai.com"],
"headers": {
"Authorization": "Bearer {{SECRET:openai-key}}",
"OpenAI-Organization": "org-abc123",
},
"secrets": {
"openai-key": "sk-proj-xyz789...",
},
},
],
"bypass": ["*.s3.amazonaws.com"],
},
},
})
# curl https://api.stripe.com/... -> gets auth header + body injected
# curl https://api.openai.com/... -> gets auth header injected
# curl https://httpbin.org/... -> allowed, no injection
# curl https://evil.com/... -> BLOCKED by allowedDomains firewall
result = await sandbox.process.exec({
"command": "curl -s https://api.stripe.com/v1/charges",
"wait_for_completion": True,
})
print(result.logs)
```
# Domain filtering
Source: https://docs.blaxel.ai/Sandboxes/Proxy-domains
Restrict which external domains, HTTP methods, and URL paths a Blaxel sandbox can reach using allowlists and denylists to prevent data exfiltration and unwanted outbound traffic.
This feature is currently in public preview and is not recommended for production use.
Domain filtering lets you control which external domains a sandbox can reach. You can define an allowlist (only listed domains are reachable) or a denylist (all domains except listed ones are reachable). Domain filtering and proxy routing are **independent configurations** — you do not need to duplicate domains across both. A domain can appear in the allowlist without having a proxy routing rule, and vice versa.
Domain filtering relies on the sandbox's tools and libraries respecting the standard proxy environment variables (`HTTP_PROXY`, `HTTPS_PROXY`). Traffic from tools that ignore proxy environment variables will not be filtered unless [domain filter enforcement](#domain-filter-enforcement) is enabled.
## Allowlist
Only the listed domains are reachable:
```typescript TypeScript theme={null}
await SandboxInstance.create({
name: "restricted-sandbox",
image: "blaxel/base-image:latest",
region: "us-was-1",
network: {
allowedDomains: ["api.stripe.com", "api.openai.com", "*.s3.amazonaws.com"],
proxy: { routing: [] },
},
});
```
```python Python theme={null}
await SandboxInstance.create({
"name": "restricted-sandbox",
"image": "blaxel/base-image:latest",
"region": "us-was-1",
"network": {
"allowedDomains": ["api.stripe.com", "api.openai.com", "*.s3.amazonaws.com"],
"proxy": {"routing": []},
},
})
```
## Denylist
All domains except the listed ones are reachable:
```typescript TypeScript theme={null}
await SandboxInstance.create({
name: "denylist-sandbox",
image: "blaxel/base-image:latest",
region: "us-was-1",
network: {
forbiddenDomains: ["*.malware.com", "evil.example.org"],
proxy: { routing: [] },
},
});
```
```python Python theme={null}
await SandboxInstance.create({
"name": "denylist-sandbox",
"image": "blaxel/base-image:latest",
"region": "us-was-1",
"network": {
"forbiddenDomains": ["*.malware.com", "evil.example.org"],
"proxy": {"routing": []},
},
})
```
When a request matches both lists, `forbiddenDomains` wins and the request is blocked. An empty `allowedDomains` allows everything that is not in `forbiddenDomains`.
## Method and path patterns
Each entry in `allowedDomains` and `forbiddenDomains` is a pattern. A bare domain like `api.stripe.com` or `*.s3.amazonaws.com` is the simplest form. You can also scope a rule to specific HTTP methods and URL paths by adding method prefixes and a path suffix:
```text theme={null}
[!]METHOD:...:domain[/path]
```
* An optional leading `!` negates the method set.
* Zero or more HTTP methods, colon-separated, before the domain (case-insensitive).
* The domain (exact, `*.suffix`, or `*`).
* An optional `/path` prefix.
| Pattern | Matches |
| - | - |
| `api.openai.com` | Any request to `api.openai.com` |
| `*.googleapis.com` | Any subdomain, but not the bare `googleapis.com` |
| `*` | Any domain |
| `GET:POST:storage.googleapis.com` | Only GET and POST to that domain |
| `!POST:storage.googleapis.com` | Every method except POST |
| `storage.googleapis.com/company-mercor*` | Paths starting with `/company-mercor` |
| `GET:storage.googleapis.com/data/` | GET requests under `/data/` |
### Methods
* No method in the pattern matches any method.
* One or more methods match only those methods (case-insensitive).
* A leading `!` negates the set: it matches every method except those listed. `!POST:domain` matches everything but POST, and `!GET:POST:domain` matches everything but GET and POST.
### Domains
* `*` matches every domain.
* `*.example.com` matches `sub.example.com` and `a.b.example.com`, but not the bare `example.com`.
* Any other value is an exact, case-insensitive match.
* A `:port` on the request is ignored when matching.
### Paths
* No path in the pattern applies the rule to every path on the domain.
* A path is matched as a start-anchored prefix. `*` matches any run of characters; every other character is literal.
* `/data/` matches `/data/` and `/data/file.txt`, but not `/data`.
* `/company-mercor*` matches `/company-mercor` and `/company-mercor-1/obj`, but not `/company-other`.
* The query string is ignored, so tokens in query parameters never affect the decision.
* The path is canonicalized before matching, so `.`, `..`, and duplicate slashes cannot slip a request past a prefix rule. A request to `/company-mercor/../other/obj` is evaluated as `/other/obj`.
A pattern that does not fit this shape is treated as a plain domain equal to the literal string. It can never widen access beyond the domain it names, so a typo fails safe and matches nothing extra.
This example allows read-only access under one path prefix and blocks writes to a sensitive subpath:
```typescript TypeScript theme={null}
await SandboxInstance.create({
name: "scoped-egress",
network: {
allowedDomains: ["GET:storage.googleapis.com/company-mercor*"],
forbiddenDomains: ["POST:storage.googleapis.com/company-mercor/secrets*"],
proxy: { routing: [] },
},
});
```
```python Python theme={null}
await SandboxInstance.create({
"name": "scoped-egress",
"network": {
"allowedDomains": ["GET:storage.googleapis.com/company-mercor*"],
"forbiddenDomains": ["POST:storage.googleapis.com/company-mercor/secrets*"],
"proxy": {"routing": []},
},
})
```
## Evaluation order
For each outbound request the proxy decides in this order:
1. If any `forbiddenDomains` pattern matches, the request is blocked.
2. Otherwise, if `allowedDomains` is non-empty and no pattern matches, the request is blocked.
3. Otherwise, the request is allowed.
Forbidden rules always take precedence over allow rules. An empty `allowedDomains` allows everything that is not forbidden. A blocked request receives an HTTP `403` response with a `Proxy-Error: firewall_blocked` header.
## Backward compatibility
Plain-domain entries behave exactly as before, so existing configurations keep working without changes. The method, path, and negation syntax is opt-in: put the richer strings into the same `allowedDomains` and `forbiddenDomains` arrays. There are no new fields.
## Domain filter enforcement
By default, domain filtering depends on the sandbox's tools respecting `HTTP_PROXY` and `HTTPS_PROXY`. To enforce filtering even for tools that bypass those variables, add a `firewall` config with `rulesets: ["proxy"]`. This locks down the sandbox's egress at the network level so that all outbound traffic must flow through the proxy.
```typescript TypeScript theme={null}
await SandboxInstance.create({
name: "enforced-sandbox",
image: "blaxel/base-image:latest",
region: "us-was-1",
network: {
firewall: { rulesets: ["proxy"] },
allowedDomains: ["httpbin.org"],
proxy: { routing: [] },
},
});
```
```python Python theme={null}
await SandboxInstance.create({
"name": "enforced-sandbox",
"image": "blaxel/base-image:latest",
"region": "us-was-1",
"network": {
"firewall": {"rulesets": ["proxy"]},
"allowedDomains": ["httpbin.org"],
"proxy": {"routing": []},
},
})
```
Firewall rulesets can only be set when the sandbox is created; changing them on an existing sandbox is rejected. Enforcement is not enabled automatically when a proxy is configured — you must opt in with the `firewall` config. Firewalled sandboxes run on Mark 3.0 infrastructure; this feature is not yet available on [Carbon (Mark 3.1)](../Infrastructure/Gens#carbon-aka-mark-31).
## Firewall + proxy combined
Firewall rules and proxy routing compose naturally:
```typescript TypeScript theme={null}
await SandboxInstance.create({
name: "locked-down",
network: {
allowedDomains: ["api.stripe.com", "api.openai.com"],
proxy: {
routing: [
{
destinations: ["api.stripe.com"],
headers: { "Authorization": "Bearer {{SECRET:stripe-key}}" },
secrets: { "stripe-key": "sk_live_..." },
},
],
},
},
});
```
```python Python theme={null}
await SandboxInstance.create({
"name": "locked-down",
"network": {
"allowedDomains": ["api.stripe.com", "api.openai.com"],
"proxy": {
"routing": [
{
"destinations": ["api.stripe.com"],
"headers": {"Authorization": "Bearer {{SECRET:stripe-key}}"},
"secrets": {"stripe-key": "sk_live_..."},
},
],
},
},
})
```
Only `api.stripe.com` and `api.openai.com` are reachable. The proxy injects credentials for Stripe requests; OpenAI requests go through unmodified.
# Proxy routing with secrets injection
Source: https://docs.blaxel.ai/Sandboxes/Proxy-secrets-injection
Inject secrets, dynamic values, headers, and body fields into outbound sandbox requests through the Blaxel proxy so code never sees raw API keys or credentials.
This feature is currently in public preview and is not recommended for production use.
The Blaxel proxy intercepts outbound HTTPS requests from the sandbox and injects headers, body fields, and secrets server-side.
Adding a proxy config to a sandbox enforces the proxy on **all** outbound requests. If some requests should not go through it, use [proxy bypass](#proxy-bypass).
## Secrets injection
### Header injection
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.create({
name: "my-sandbox",
image: "blaxel/base-image:latest",
region: "us-was-1",
network: {
proxy: {
routing: [
{
destinations: ["api.stripe.com"],
headers: {
"Authorization": "Bearer {{SECRET:stripe-key}}",
"Stripe-Version": "2024-12-18.acacia",
},
secrets: {
"stripe-key": "sk_live_...",
},
},
],
},
},
});
```
```python Python theme={null}
from blaxel.core.sandbox import SandboxInstance
sandbox = await SandboxInstance.create({
"name": "my-sandbox",
"image": "blaxel/base-image:latest",
"region": "us-was-1",
"network": {
"proxy": {
"routing": [
{
"destinations": ["api.stripe.com"],
"headers": {
"Authorization": "Bearer {{SECRET:stripe-key}}",
"Stripe-Version": "2024-12-18.acacia",
},
"secrets": {
"stripe-key": "sk_live_...",
},
},
],
},
},
})
```
Code inside the sandbox calls `api.stripe.com` normally - the proxy intercepts the request, injects the `Authorization` and `Stripe-Version` headers with the resolved secret, and forwards it. The sandbox never sees the raw API key.
### Body injection (POST requests)
```typescript TypeScript theme={null}
await SandboxInstance.create({
name: "body-injection",
network: {
proxy: {
routing: [
{
destinations: ["api.stripe.com"],
headers: {
"Authorization": "Bearer {{SECRET:stripe-key}}",
},
body: {
"api_key": "{{SECRET:stripe-key}}",
},
secrets: {
"stripe-key": "sk_live_...",
},
},
],
},
},
});
```
```python Python theme={null}
await SandboxInstance.create({
"name": "body-injection",
"network": {
"proxy": {
"routing": [
{
"destinations": ["api.stripe.com"],
"headers": {
"Authorization": "Bearer {{SECRET:stripe-key}}",
},
"body": {
"api_key": "{{SECRET:stripe-key}}",
},
"secrets": {
"stripe-key": "sk_live_...",
},
},
],
},
},
})
```
The proxy merges body fields into outbound POST/PUT/PATCH JSON payloads. User-sent fields are preserved; injected fields are added alongside them.
### Multiple routing rules
```typescript TypeScript theme={null}
await SandboxInstance.create({
name: "multi-route",
network: {
proxy: {
routing: [
{
destinations: ["api.stripe.com"],
headers: { "Authorization": "Bearer {{SECRET:stripe-key}}" },
secrets: { "stripe-key": "sk_live_..." },
},
{
destinations: ["api.openai.com"],
headers: { "Authorization": "Bearer {{SECRET:openai-key}}" },
secrets: { "openai-key": "sk-proj-..." },
},
],
bypass: ["*.s3.amazonaws.com"],
},
},
});
```
```python Python theme={null}
await SandboxInstance.create({
"name": "multi-route",
"network": {
"proxy": {
"routing": [
{
"destinations": ["api.stripe.com"],
"headers": {"Authorization": "Bearer {{SECRET:stripe-key}}"},
"secrets": {"stripe-key": "sk_live_..."},
},
{
"destinations": ["api.openai.com"],
"headers": {"Authorization": "Bearer {{SECRET:openai-key}}"},
"secrets": {"openai-key": "sk-proj-..."},
},
],
"bypass": ["*.s3.amazonaws.com"],
},
},
})
```
Secrets are scoped per rule — the Stripe key is never injected into OpenAI requests and vice versa.
### Global catch-all rule
```typescript TypeScript theme={null}
await SandboxInstance.create({
name: "global-auth",
network: {
proxy: {
routing: [
{
destinations: ["*"],
headers: {
"X-Global-Auth": "Bearer {{SECRET:global-key}}",
},
secrets: {
"global-key": "token-xyz",
},
},
],
},
},
});
```
```python Python theme={null}
await SandboxInstance.create({
"name": "global-auth",
"network": {
"proxy": {
"routing": [
{
"destinations": ["*"],
"headers": {"X-Global-Auth": "Bearer {{SECRET:global-key}}"},
"secrets": {"global-key": "token-xyz"},
},
],
},
},
})
```
The `["*"]` destination matches all proxied traffic.
### Secret interpolation
Secrets are referenced in headers and body values using the `{{SECRET:name}}` syntax:
```text theme={null}
"Authorization": "Bearer {{SECRET:api-token}}" → "Bearer tok_live_abc123"
"X-Multi": "{{SECRET:part-a}}-{{SECRET:part-b}}" → "ALPHA-BETA"
"X-Plain": "no-secret-here" → "no-secret-here" (unchanged)
```
* Multiple `{{SECRET:...}}` placeholders can appear in a single value
* Secrets are resolved server-side by the proxy — the sandbox runtime never sees raw secret values
* Secrets are write-only: the `secrets` field is stripped from API responses
* Secrets are scoped per routing rule: a secret defined on route A cannot be resolved by route B
* User code inside the sandbox can also send `{{SECRET:name}}` in its own request headers or body — the proxy will resolve them if the secret exists on the matching route
## Proxy bypass
Domains listed in `bypass` skip the proxy tunnel entirely (direct connection):
```typescript TypeScript theme={null}
await SandboxInstance.create({
name: "bypass-only",
network: {
proxy: {
bypass: ["*.s3.amazonaws.com", "169.254.169.254"],
},
},
});
```
```python Python theme={null}
await SandboxInstance.create({
"name": "bypass-only",
"network": {
"proxy": {
"bypass": ["*.s3.amazonaws.com", "169.254.169.254"],
},
},
})
```
S3 and metadata endpoint traffic goes direct; everything else routes through the proxy.
## Dynamic value injection
Alongside secrets, you can inject values that are generated fresh for every request using the `{{FUNC:name(args)}}` syntax. Use them for idempotency keys, request IDs, timestamps, nonces, and other one-time values.
```typescript TypeScript theme={null}
await SandboxInstance.create({
name: "idempotency-key",
network: {
proxy: {
routing: [
{
destinations: ["api.openai.com"],
headers: {
"X-Request-Id": "req-{{FUNC:uuid()}}",
},
},
],
},
},
});
```
```python Python theme={null}
await SandboxInstance.create({
"name": "idempotency-key",
"network": {
"proxy": {
"routing": [
{
"destinations": ["api.openai.com"],
"headers": {
"X-Request-Id": "req-{{FUNC:uuid()}}",
},
},
],
},
},
})
```
Each request through this rule gets a different `X-Request-Id`. Function names are case-insensitive, and each occurrence is evaluated independently, so `{{FUNC:uuid()}}-{{FUNC:uuid()}}` produces two different UUIDs.
### Available functions
| Placeholder | Expands to |
| - | - |
| `{{FUNC:uuid()}}` / `{{FUNC:uuidv4()}}` | Random v4 UUID |
| `{{FUNC:uuidv7()}}` | Time-ordered v7 UUID |
| `{{FUNC:timestamp()}}` | Current Unix time in seconds |
| `{{FUNC:timestamp_ms()}}` | Current Unix time in milliseconds |
| `{{FUNC:timestamp_ns()}}` | Current Unix time in nanoseconds |
| `{{FUNC:datetime()}}` / `{{FUNC:iso8601()}}` | Current UTC time, RFC 3339 |
| `{{FUNC:date()}}` | Current UTC date, `YYYY-MM-DD` |
| `{{FUNC:time()}}` | Current UTC time of day, `HH:MM:SS` |
| `{{FUNC:randint()}}` / `{{FUNC:randint(n)}}` | Random integer in `[0, 2147483647)` or `[0, n)` |
| `{{FUNC:randhex()}}` / `{{FUNC:randhex(n)}}` | 32 or `n` random hex characters (`n` up to 1024) |
| `{{FUNC:randstr()}}` / `{{FUNC:randstr(n)}}` | 16 or `n` random alphanumerics (`n` up to 1024) |
| `{{FUNC:nonce()}}` | 32 random hex characters |
Random functions use a cryptographically secure random source. `randstr` draws uniformly from `[a-zA-Z0-9]`.
### Where dynamic values apply
`{{FUNC:*}}` expansion runs only in the header and body values you define on a routing rule. It never runs on data that code inside the sandbox sends. Request headers and body content your code sends are resolved for `{{SECRET:name}}` only; any `{{FUNC:*}}` text your code sends is passed through unchanged.
| Source | `{{SECRET:*}}` | `{{FUNC:*}}` |
| - | :-: | :-: |
| Routing rule header and body values | Yes | Yes |
| Request headers sent by sandbox code | Yes | No (left as text) |
| Request body sent by sandbox code | Yes | No (left as text) |
Within a single value, functions are expanded before secrets are substituted. A secret whose value happens to contain `{{...}}` syntax is inserted verbatim and never re-interpreted as a placeholder.
Functions expand only in the values you configure on a routing rule, never in data your sandbox code sends. This keeps a caller from packing a request with many expensive placeholders to force disproportionate work.
### Reading generated values from the response
Because generated values are produced inside the proxy, each injected header or body field that used a function is echoed back on the response as its own header, keyed by the injection target:
```text theme={null}
X-Blaxel-Func-: =[, = ...]
```
For example, injecting `{{FUNC:uuid()}}` into the `X-Request-Id` header adds the following response header:
```text theme={null}
X-Blaxel-Func-X-Request-Id: uuid=3f2a...c9
```
The value lists the functions used in that target as `name=value` tokens, in generation order. Only function values are echoed. Resolved `{{SECRET:*}}` values are never returned to your code. A value is echoed only when its injection resolves cleanly: if a value combines a function with a secret that fails to resolve, the whole injection is skipped and nothing is echoed for it.
### Fail-safe behavior and limits
Anything the proxy cannot resolve is left as literal text rather than blanked out or failing the request:
* Unknown function, for example `{{FUNC:notafunction()}}`
* Malformed placeholder with missing parentheses, for example `{{FUNC:uuid}}`
* Invalid argument, for example `{{FUNC:randhex(abc)}}` or `{{FUNC:randint(0)}}`
* Over the size cap, for example `{{FUNC:randstr(5000)}}`
* Beyond the per-request budget
Two limits bound the work any single request can trigger:
* Per-function output: `randstr` and `randhex` cap their length argument at 1024 characters
* Per-request count: at most 10 functions are expanded per request, counted across all header and body values combined. Any beyond the limit are left as literal text
## Reading proxy config from an existing sandbox
After creation or retrieval, network config is available as typed model attributes:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
const network = sandbox.spec.network;
if (network?.proxy?.routing) {
for (const route of network.proxy.routing) {
console.log(route.destinations);
console.log(route.headers["Authorization"]);
}
if (network.proxy.bypass) {
console.log(network.proxy.bypass);
}
}
if (network?.allowedDomains) {
console.log(network.allowedDomains);
}
```
```python Python theme={null}
from blaxel.core.sandbox import SandboxInstance
from blaxel.core.client.types import Unset
sandbox = await SandboxInstance.get("my-sandbox")
network = sandbox.spec.network # SandboxNetwork (or Unset)
if not isinstance(network, Unset) and not isinstance(network.proxy, Unset):
for route in network.proxy.routing:
print(route.destinations)
print(route.headers["Authorization"])
if not isinstance(network.proxy.bypass, Unset):
print(network.proxy.bypass)
if not isinstance(network, Unset) and not isinstance(network.allowed_domains, Unset):
print(network.allowed_domains)
```
# Sandbox schedules
Source: https://docs.blaxel.ai/Sandboxes/Schedules
Run a command inside a Blaxel sandbox on a recurring cron schedule, a future datetime, or after a delay, with execution history per run.
Schedules run a process inside a sandbox automatically, without you triggering it from your client. Use them for recurring jobs (a nightly cleanup, a periodic sync), a one-off task at a future time, or a task that runs once after a delay. Each schedule fires a command through the sandbox process API and records the outcome in an execution history.
A sandbox can hold up to 100 schedules.
## Schedule types
Every schedule has a `type` and a `value` that together define when it fires:
* `cron`: recurring. `value` is a 5-field cron expression (e.g. `0 8 * * 1-5` for 8am on weekdays). Cron granularity is one minute.
* `at`: one-off. `value` is an RFC 3339 datetime (e.g. `2026-07-01T09:00:00Z`). The schedule fires once, then deletes itself.
* `sleep`: one-off. `value` is a duration from now (e.g. `2h`, `30m`, `7d`). On creation it is resolved to an `at` schedule, so a created `sleep` is returned with `type: at` and a concrete datetime.
One-off schedules (`at` and `sleep`) are removed automatically after they fire. A `cron` schedule keeps firing until you delete it.
## Schedule input
The `input` object configures the process that each firing starts:
| Field | Type | Description |
| - | - | - |
| `command` | string | Shell command to run inside the sandbox. Required. |
| `name` | string | Optional process name, used to look up status and logs. |
| `env` | object | Environment variables for the process. Values are encrypted at rest and masked in API responses. |
| `workingDir` | string | Working directory for the command. |
| `keepAlive` | boolean | Keep the sandbox awake (disable scale-to-zero) while the process runs. Defaults to `true`. |
| `timeout` | integer | Process timeout in seconds. Defaults to `600`. Set to `0` for no timeout. |
## Create a schedule
Create a recurring schedule on an existing sandbox:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
const schedule = await sandbox.schedules.create({
type: "cron",
value: "0 8 * * 1-5",
input: {
command: "python cleanup.py",
env: { TARGET: "staging" },
},
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
schedule = await sandbox.schedules.create({
"type": "cron",
"value": "0 8 * * 1-5",
"input": {
"command": "python cleanup.py",
"env": {"TARGET": "staging"},
},
})
```
```bash REST theme={null}
curl -X POST "https://api.blaxel.ai/v0/sandboxes/my-sandbox/schedules" \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: $BL_WORKSPACE" \
-H "Content-Type: application/json" \
-d '{
"type": "cron",
"value": "0 8 * * 1-5",
"input": { "command": "python cleanup.py", "env": { "TARGET": "staging" } }
}'
```
Create a one-off task that runs once, 2 hours from now:
```typescript TypeScript theme={null}
const schedule = await sandbox.schedules.create({
type: "sleep",
value: "2h",
input: { command: "python report.py" },
});
// Returned resolved to type "at" with a concrete datetime
```
```python Python theme={null}
schedule = await sandbox.schedules.create({
"type": "sleep",
"value": "2h",
"input": {"command": "python report.py"},
})
# Returned resolved to type "at" with a concrete datetime
```
```bash REST theme={null}
curl -X POST "https://api.blaxel.ai/v0/sandboxes/my-sandbox/schedules" \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: $BL_WORKSPACE" \
-H "Content-Type: application/json" \
-d '{ "type": "sleep", "value": "2h", "input": { "command": "python report.py" } }'
```
## List and manage schedules
Read, update, and delete schedules by their `id`:
```typescript TypeScript theme={null}
// List schedules (one page; iterate with `for await` to walk every page)
const schedules = await sandbox.schedules.list({ limit: 50 });
for (const schedule of schedules.data) {
console.log(schedule.id);
}
// Get one by id
const schedule = await sandbox.schedules.get("schedule-0");
// Update it
await sandbox.schedules.update("schedule-0", {
type: "cron",
value: "0 9 * * 1-5",
input: { command: "python cleanup.py" },
});
// Delete it (stops further firings)
await sandbox.schedules.delete("schedule-0");
```
```python Python theme={null}
# List schedules (one page; use auto_paging_iter() to walk every page)
schedules = await sandbox.schedules.list(limit=50)
for schedule in schedules.data:
print(schedule.id)
# Get one by id
schedule = await sandbox.schedules.get("schedule-0")
# Update it
await sandbox.schedules.update("schedule-0", {
"type": "cron",
"value": "0 9 * * 1-5",
"input": {"command": "python cleanup.py"},
})
# Delete it (stops further firings)
await sandbox.schedules.delete("schedule-0")
```
```bash REST theme={null}
# List
curl "https://api.blaxel.ai/v0/sandboxes/my-sandbox/schedules" \
-H "Authorization: Bearer $BL_API_KEY" -H "X-Blaxel-Workspace: $BL_WORKSPACE"
# Delete
curl -X DELETE "https://api.blaxel.ai/v0/sandboxes/my-sandbox/schedules/schedule-0" \
-H "Authorization: Bearer $BL_API_KEY" -H "X-Blaxel-Workspace: $BL_WORKSPACE"
```
Deleting a schedule removes it from the underlying scheduler, so no further runs fire.
## Execution history
Each firing records one execution. The `statusCode` is the HTTP status returned when the command was submitted to the sandbox, not the command's own exit code: the scheduler does not wait for the command to finish. A 2xx or 3xx status means the command was accepted.
List the execution history of a sandbox. It returns the runs of every schedule on the sandbox, newest first:
```typescript TypeScript theme={null}
const executions = await sandbox.schedules.executions({ limit: 50 });
for await (const execution of executions) {
console.log(execution.scheduleId, execution.statusCode, execution.processName);
}
```
```python Python theme={null}
executions = await sandbox.schedules.executions(limit=50)
async for execution in executions.auto_paging_iter():
print(execution.schedule_id, execution.status_code, execution.process_name)
```
```bash REST theme={null}
curl "https://api.blaxel.ai/v0/sandboxes/my-sandbox/schedule-executions" \
-H "Authorization: Bearer $BL_API_KEY" -H "X-Blaxel-Workspace: $BL_WORKSPACE"
```
Each record carries `scheduleId`, `statusCode`, `executedAt`, and the `processName` used to start the process, so you can look up its logs (see [Log streaming](/Sandboxes/Log-streaming)). History is a ring buffer per schedule: once it reaches `maxExecutions` (default 100), recording a new run drops the oldest.
## Manage from the console
You can create and inspect schedules from the [Blaxel Console](https://app.blaxel.ai), on the **Schedules** tab of a sandbox. The tab lists every schedule with its type, timing, and command, and shows the execution history for each one.
Execute and manage processes in sandboxes.
Access logs generated in a sandbox.
Automatically delete sandboxes based on specific conditions.
# Client-side sessions
Source: https://docs.blaxel.ai/Sandboxes/Sessions
Operate Blaxel sandboxes directly from a frontend client using time-limited, scoped session tokens generated by your backend, with no proxy needed.
In many situations, you’ll need to operate a sandbox from a frontend client. When doing so, you cannot share the Blaxel credentials needed to access the sandbox. The solution is to use **sessions.**
Sessions are created for a sandbox from a backend server (using Blaxel credentials) and then shared with the frontend client, allowing the browser to connect to the sandbox.
From a session, you can:
* only interact with the [sandbox API](https://docs.blaxel.ai/api-reference/filesystem/get-file-or-directory-information) (i.e. manage the sandbox filesystem, processes and logs).
From a session, you cannot:
* interact with Blaxel API to create other preview URLs or sessions. These operations must be done server-side.
## Basic example
Create a temporary backend session to access a sandbox instance from your client application. The main parameter for this is `expiresAt`, a `Date()` corresponding to the expiration date.
```typescript TypeScript theme={null}
// From your backend
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
const session = await sandbox.sessions.create({ expiresAt });
```
```python Python theme={null}
# From your backend
from datetime import datetime, timedelta, UTC
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
expires_at = datetime.now(UTC) + timedelta(hours=24)
session = await sandbox.sessions.create({"expires_at": expires_at})
```
```tsx theme={null}
/// From your frontend:
import { SandboxInstance } from "@blaxel/core";
const sandboxWithSession = await SandboxInstance.fromSession(session)
```
### Create if expired
This helper function either retrieves an existing session or creates a new one if it expired. You can optionally pass `delta` (default: 1 hour), the time window in milliseconds before actual expiration when a session should still be recreated.
```typescript TypeScript theme={null}
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
const session = await sandbox.sessions.createIfExpired(
{ expiresAt },
60000 // delta in milliseconds
);
```
```python Python theme={null}
expires_at = datetime.now(UTC) + timedelta(hours=24)
session = await sandbox.sessions.create_if_expired(
{"expiresAt": expires_at},
delta_seconds=60000
)
```
## Example (NextJS)
The following example demonstrates a full implementation of sessions in a backend server and frontend client using NextJS.
### Server code (backend)
```typescript expandable theme={null}
import { NextResponse } from 'next/server';
import { SandboxInstance } from "@blaxel/core";
const SANDBOX_NAME = 'my-sandbox';
const responseHeaders = {
"Access-Control-Allow-Origin": "http://localhost:3000",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
"Vary": "Origin"
}
export async function GET() {
// Get or create sandbox
const sandbox = await SandboxInstance.createIfNotExists({
name: SANDBOX_NAME,
image: "blaxel/base-image:latest",
memory: 4096,
region: "us-pdx-1"
});
// Create session (24 hours expiry)
const session = await sandbox.sessions.create({
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000)
});
// Create preview for port 3000
const preview = await sandbox.previews.create({
metadata: { name: "app-preview" },
spec: {
port: 3000,
public: true,
responseHeaders: responseHeaders
}
});
return NextResponse.json({
session,
preview_url: preview.spec?.url
});
}
```
### Client code (frontend)
```typescript expandable theme={null}
'use client'
import { SandboxInstance } from "@blaxel/core";
import { useState, useEffect } from "react";
export default function SandboxClient() {
const [sandbox, setSandbox] = useState(null);
const [previewUrl, setPreviewUrl] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
initializeSandbox();
}, []);
async function initializeSandbox() {
// Get session from backend
const response = await fetch('/api/sandbox');
const { session, preview_url } = await response.json();
// Create sandbox from session
const sandboxInstance = await SandboxInstance.fromSession(session);
setSandbox(sandboxInstance);
setPreviewUrl(preview_url);
// Start development server
await sandboxInstance.process.exec({
name: "dev-server",
command: "npm run dev",
workingDir: "/app",
waitForPorts: [3000]
});
setLoading(false);
}
if (loading) return
Loading sandbox...
;
return (
Sandbox Demo
{previewUrl && (
)}
);
}
```
# Standby control
Source: https://docs.blaxel.ai/Sandboxes/Standby-control
Control when sandboxes remain active by managing WebSocket connections, tab visibility, auto-disconnect behavior, and activity-based timeouts.
Sandboxes stay active as long as there's an active connection to them, typically through a WebSocket connection. When a browser tab becomes inactive, the WebSocket should disconnect after some time, but this behavior depends on the specific browser implementation.
You can also use [process keep-alive](./Processes#sandbox-keep-alive) to keep the sandbox running when you launch a process, even if there isn't an active connection to it.
There are no built-in stop or start functions available in the SDKs to manage standby mode. This means you'll need to rely on other approaches to better control when sandboxes remain active:
1. Hide iframe when tab is inactive
Use JavaScript events to detect when a user is not active on the tab:
* Listen for tab visibility events that indicate when the user switches away from your tab
* When the user becomes inactive, hide the iframe containing the sandbox preview
* Show the iframe again when the user returns to the tab
2. Implement auto-disconnect on tab switch
Some browsers (like Chrome) may keep WebSockets alive even when tabs are inactive to improve performance. You can implement an auto-disconnect feature that:
* Detects when users switch tabs
* Automatically disconnects the WebSocket connection
* Reconnects when the user returns
3. Use activity-based timeouts
Set up a timer system in your interface that:
* Monitors user activity (typing, interactions, etc.)
* Hides the preview after a period of inactivity
* Prompts the user to confirm they're still using the sandbox
You can use the SDKs from the frontend if you have the right headers set on the session. Use the [`sandbox.sessions.create` function](/Sandboxes/Sessions) to manage sessions.
Here is an example of auto disconnect on tab switch with Vite through a plugin:
The code below is illustrative and not intended for production use.
```typescript theme={null}
// vite.config.ts
import { defineConfig } from 'vite'
import { hmrVisibility } from './vite-plugin-hmr-visibility'
// https://vite.dev/config/
export default defineConfig({
plugins: [
hmrVisibility({
disconnectDelay: 2000, // Wait 2 seconds before disconnecting
debug: true, // Enable debug logging
}),
],
server: {
// Prevent Vite from aggressive reconnection attempts
hmr: {
overlay: false, // Disable error overlay which can trigger reconnections
},
},
})
// vite-plugin-hmr-visibility.ts
/**
* Vite Plugin: HMR Visibility Manager
* Automatically disconnects/reconnects HMR websocket based on tab visibility
* to reduce serverless billing costs
*/
import type { Plugin } from 'vite'
export interface HMRVisibilityOptions {
/**
* Grace period before disconnecting (ms)
* @default 2000
*/
disconnectDelay?: number
/**
* Enable debug logging
* @default false
*/
debug?: boolean
}
export function hmrVisibility(options: HMRVisibilityOptions = {}): Plugin {
const {
disconnectDelay = 2000,
debug = false,
} = options
return {
name: 'vite-plugin-hmr-visibility',
apply: 'serve', // Only apply in dev mode
transformIndexHtml() {
// Inject the HMR visibility manager as an inline script
return [
{
tag: 'script',
attrs: { type: 'module' },
children: `
// HMR Visibility Manager - Disconnects HMR when tab is hidden
(function() {
const log = (...args) => {
if (${JSON.stringify(debug)}) {
console.log('[HMR Visibility]', ...args);
}
};
let disconnectTimer = null;
let isTabVisible = !document.hidden;
const DISCONNECT_DELAY = ${JSON.stringify(disconnectDelay)};
// Store reference to original functions
const OriginalWebSocket = window.WebSocket;
const OriginalSetTimeout = window.setTimeout;
const OriginalSetInterval = window.setInterval;
let hmrSocket = null;
let shouldBlockReconnect = false;
let blockedTimers = new Set();
// Patch setTimeout and setInterval to block Vite's reconnection timers
window.setTimeout = function(callback, delay, ...args) {
// Block timers when tab is hidden and it looks like a reconnection attempt
if (shouldBlockReconnect && delay && delay >= 500 && delay <= 5000) {
const callbackStr = callback.toString();
if (callbackStr.includes('connect') || callbackStr.includes('WebSocket') || callbackStr.includes('ws')) {
log('Blocked reconnection timer (setTimeout)');
const timerId = OriginalSetTimeout(() => {}, 999999999);
blockedTimers.add(timerId);
return timerId;
}
}
return OriginalSetTimeout.call(this, callback, delay, ...args);
};
window.setInterval = function(callback, delay, ...args) {
// Block intervals when tab is hidden
if (shouldBlockReconnect) {
const callbackStr = callback.toString();
if (callbackStr.includes('connect') || callbackStr.includes('WebSocket') || callbackStr.includes('ws')) {
log('Blocked reconnection interval (setInterval)');
const timerId = OriginalSetInterval(() => {}, 999999999);
blockedTimers.add(timerId);
return timerId;
}
}
return OriginalSetInterval.call(this, callback, delay, ...args);
};
// Patch WebSocket to track and block HMR connections
window.WebSocket = function(url, protocols) {
// Block new WebSocket connections when tab is hidden
if (shouldBlockReconnect) {
log('Blocked WebSocket connection attempt while tab is hidden:', url);
// Return a fake WebSocket that stays in CONNECTING state forever
const fakeWs = {
readyState: 0, // CONNECTING (keeps Vite waiting)
close: () => { log('Fake WebSocket close called'); },
send: () => { log('Fake WebSocket send called'); },
addEventListener: () => {},
removeEventListener: () => {},
dispatchEvent: () => false,
onopen: null,
onclose: null,
onerror: null,
onmessage: null,
};
return fakeWs;
}
const ws = new OriginalWebSocket(url, protocols);
// Track HMR WebSocket and intercept its event handlers
if (typeof url === 'string') {
hmrSocket = ws;
log('HMR WebSocket created and tracked');
// Intercept the onclose setter to control reconnection behavior
let userOnClose = null;
Object.defineProperty(ws, 'onclose', {
get() { return userOnClose; },
set(handler) {
userOnClose = function(event) {
// If we're blocking reconnect, don't call Vite's onclose handler
if (shouldBlockReconnect) {
log('Suppressed onclose handler - blocking reconnection');
return;
}
// Otherwise, call the original handler
if (handler) {
handler.call(this, event);
}
};
},
configurable: true
});
// Also intercept addEventListener for 'close' events
const originalAddEventListener = ws.addEventListener;
ws.addEventListener = function(type, listener, options) {
if (type === 'close') {
const wrappedListener = function(event) {
if (shouldBlockReconnect) {
log('Suppressed close event listener - blocking reconnection');
return;
}
listener.call(this, event);
};
return originalAddEventListener.call(this, type, wrappedListener, options);
}
return originalAddEventListener.call(this, type, listener, options);
};
}
return ws;
};
// Copy static properties from original WebSocket
Object.setPrototypeOf(window.WebSocket, OriginalWebSocket);
window.WebSocket.prototype = OriginalWebSocket.prototype;
// Copy static constants
Object.defineProperty(window.WebSocket, 'CONNECTING', { value: 0, enumerable: true });
Object.defineProperty(window.WebSocket, 'OPEN', { value: 1, enumerable: true });
Object.defineProperty(window.WebSocket, 'CLOSING', { value: 2, enumerable: true });
Object.defineProperty(window.WebSocket, 'CLOSED', { value: 3, enumerable: true });
const disconnectHMR = () => {
if (hmrSocket && (hmrSocket.readyState === 0 || hmrSocket.readyState === 1)) {
log('Disconnecting HMR websocket (tab hidden) - saving costs ✅');
hmrSocket.close(1000, 'Tab hidden');
hmrSocket = null;
}
shouldBlockReconnect = true;
};
const reconnectHMR = () => {
shouldBlockReconnect = false;
// Clear any blocked timers
blockedTimers.forEach(timerId => {
try {
clearTimeout(timerId);
clearInterval(timerId);
} catch (e) {}
});
blockedTimers.clear();
// If HMR was disconnected, reload to reconnect
if (!hmrSocket || hmrSocket.readyState === 3) {
log('Reloading page to restore HMR connection');
setTimeout(() => window.location.reload(), 100);
}
};
// Handle tab visibility changes
document.addEventListener('visibilitychange', () => {
isTabVisible = !document.hidden;
if (document.hidden) {
log('Tab hidden - will disconnect HMR in ' + DISCONNECT_DELAY + 'ms');
disconnectTimer = OriginalSetTimeout(() => {
if (document.hidden) {
disconnectHMR();
}
}, DISCONNECT_DELAY);
} else {
log('Tab visible - allowing HMR reconnection');
// Clear disconnect timer if tab becomes visible again
if (disconnectTimer) {
clearTimeout(disconnectTimer);
disconnectTimer = null;
}
OriginalSetTimeout(() => {
reconnectHMR();
}, 100);
}
});
// Handle page freeze events
window.addEventListener('freeze', () => {
log('Page freeze detected - disconnecting HMR immediately');
disconnectHMR();
}, { capture: true });
window.addEventListener('resume', () => {
log('Page resume detected - allowing HMR reconnection');
OriginalSetTimeout(() => {
reconnectHMR();
}, 100);
}, { capture: true });
log('HMR Visibility Manager initialized - will block reconnection attempts when tab is hidden');
})();
`,
injectTo: 'head-prepend',
},
]
},
}
}
```
# Build reusable sandbox images with Dockerfiles
Source: https://docs.blaxel.ai/Sandboxes/Templates
Create reusable sandbox images with pre-configured tools, languages, and frameworks using Dockerfiles. Deploy new sandboxes from images in seconds.
Sandbox images allow you to create customized & reusable sandbox environments. They define the tools, languages, frameworks, and configurations that will be available when you spawn a new sandbox instance.
Images are particularly useful for teams who need standardized environments or for creating many specialized sandboxes for repeated use cases (codegen agent, Git PR reviews agent, etc.).
To free storage occupied by images you no longer use, see [clean up unused images](#clean-up-unused-images).
## What are sandbox images?
Sandbox images are pre-configured container images that serve as blueprints for creating sandboxes. Each image includes:
* **Base environment**: Operating system and runtime configurations
* **Tools**: Languages, frameworks, and development tools
* **Configuration**: Environment variables, startup scripts, …
* **Resources**: Memory allocated
When you create a sandbox from an image, Blaxel provisions a new instance with all the specifications defined in that image.
### How sandbox images work
1. **Initial setup**: Follow this guide to create your sandbox image for the first time. Use `bl push` to publish the image to Blaxel's registry without creating a new sandbox or affecting existing sandboxes, or `bl deploy` to push the image and also create a sandbox from it.
2. **Build phase**: Your Dockerfile is used to create a container with all required tools and configurations. Blaxel then [slims the image](#image-slimming), removing files that are usually only needed at build time.
3. **Initialization phase**: The sandbox API is injected and startup commands are executed.
4. **Instantiation**: New sandboxes can be spawned from this image in seconds.
You cannot directly use "library" container images (such as those hosted on Docker Hub and other registries) as sandbox images. Instead, you must create one or more custom images for your sandboxes using Dockerfiles and ensure that each image includes Blaxel's sandbox API binary. This is necessary for sandbox functionality like process management and file operations.
## Pre-built images
Blaxel provides a library of pre-built container images for common needs.
| Image | Description |
| - | - |
| [`blaxel/base-image:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/base-image) | Minimal environment with Node.js 22 (Alpine) and Python 3.12 |
| [`blaxel/py-app:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/py-app) | Python 3.12 development environment |
| [`blaxel/ts-app:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/ts-app) | TypeScript development environment with Node.js 22 (slim) |
| [`blaxel/node:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/node) | Node.js development environment with Node.js 23 (Alpine) |
| [`blaxel/claude-code:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/claude-code) | Claude Code CLI environment with Node.js 24 (slim) |
| [`blaxel/codex:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/codex) | Codex CLI environment with Node.js 24 (slim) |
| [`blaxel/nextjs:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/nextjs) | Next.js development environment with Node.js 22 (Alpine) |
| [`blaxel/vite:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/vite) | Vite + React + TS development environment with Node.js 22 (Alpine) |
| [`blaxel/astro:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/astro) | Astro development environment with Node.js 22 (Alpine) |
| [`blaxel/expo:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/expo) | React Native (Expo) development with Node.js 22 (Alpine) |
| [`blaxel/chromium:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/chromium) | Headless Chromium environment with Chrome 124 (Alpine) |
| [`blaxel/lightpanda:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/lightpanda) | Lightweight headless browser |
| [`blaxel/playwright-chromium:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/playwright-chromium) | Playwright + Chromium browser automation environment with Node.js 20 |
| [`blaxel/playwright-firefox:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/playwright-firefox) | Playwright + Firefox browser automation environment with Node.js 20 |
| [`blaxel/docker-in-sandbox:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/docker-in-sandbox) | Docker environment |
| [`blaxel/xfce-vnc:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/xfce-vnc) | XFCE desktop environment with VNC |
| [`blaxel/cua-xfce:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/cua-xfce) | XFCE desktop environment with CUA |
| [`blaxel/jupyter-notebook:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/jupyter-notebook) | Jupyter Notebook with Python 3.12 |
| [`blaxel/jupyter-server:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/jupyter-server) | Jupyter Server with Python 3.12 |
| [`blaxel/benchmark:latest`](https://github.com/blaxel-ai/sandbox/tree/main/hub/benchmark) | Sandbox benchmarking environment |
## Custom images
You can also customize an image for specific needs.
### Create a sandbox image
You can create a customized sandbox image using the Blaxel CLI or REST API.
#### Blaxel CLI
##### 1. Initialize an image
Start by creating a new sandbox image using the Blaxel CLI:
```bash theme={null}
bl new sandbox mytemplate
```
This creates a new directory with the essential image files:
```text theme={null}
mytemplate/
├── blaxel.toml # Image configuration
├── Makefile # Build commands
├── Dockerfile # Defines the sandbox environment
└── entrypoint.sh # Initialization script
```
##### 2. Customize the Dockerfile
The Dockerfile is the heart of your image. It defines what will be available in your sandbox environment.
```docker theme={null}
# Choose a base image
FROM node:22-alpine
# Set working directory
WORKDIR /app
# Copy sandbox API (required)
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
# Install system dependencies
RUN apk update && apk add --no-cache \
git curl python3 make g++ netcat-openbsd \
&& rm -rf /var/cache/apk/*
# Copy and set up entrypoint
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
```
**Always include** the *sandbox-api* binary from the Blaxel base image. This is required for sandbox functionality like process management and file operations.
This example installs `make` and `g++`. Those compilers are available in `RUN` steps, then removed by [image slimming](#image-slimming) before the sandbox starts. Set `slim = false` when the running sandbox needs them.
##### 3. Configure image settings
The `blaxel.toml` file defines your image’s runtime configuration:
```toml theme={null}
name = "mytemplate"
type = "sandbox"
description = "Full-stack development environment with Node.js and Python"
[runtime]
generation = "mk3"
memory = 8192 # 8GB RAM
# Define exposed ports
[[runtime.ports]]
name = "dev-server"
target = 3000
protocol = "tcp"
[[runtime.ports]]
name = "another-api"
target = 8888
protocol = "tcp"
# Set environment variables
[env]
NODE_ENV = "development"
PYTHON_ENV = "development"
```
Variables set here apply to every sandbox created from this image. On the new sandbox generation, you can also [update variables on a running sandbox](/Sandboxes/Variables-and-secrets#update-variables-on-an-existing-sandbox) or [give them a new value when you fork a sandbox](/Sandboxes/Fork#fork-with-new-environment-variables). See the [variables and secrets](/Sandboxes/Variables-and-secrets) documentation for details.
##### 4. Define initialization
The `entrypoint.sh` script runs when a sandbox is created from this image:
```bash theme={null}
#!/bin/sh
# Start the sandbox API (required)
/usr/local/bin/sandbox-api &
# Wait for sandbox API to be ready
echo "Waiting for sandbox API..."
while ! nc -z 127.0.0.1 8080; do
sleep 0.1
done
echo "Sandbox API ready"
# Initialize your environment
echo "Setting up development environment..."
# Example: Start a development server in the background
if [ -f /app/package.json ]; then
cd /app
npm install
# Execute curl command, we execute it through the sandbox-api so that you can access logs,
# process status and everything you can do with the sandbox api
echo "Running Next.js dev server..."
curl http://127.0.0.1:8080/process -X POST -d '{"workingDir": "/app", "command": "npm run dev", "waitForCompletion": false}' -H "Content-Type: application/json"
fi
# Keep the container running
wait
```
##### 5. Build and test locally
Before pushing the image to Blaxel, test it locally:
```bash theme={null}
# Build the Docker image
make build
# Run locally to test
make run
# Access your sandbox-api on exposed ports
# e.g., http://127.0.0.1:8080
# Example: curl http://127.0.0.1:8080/process
```
##### 6. Push the image
Once satisfied with your configuration, push the image to Blaxel:
```bash theme={null}
bl push
```
This will:
1. Build your Docker image
2. Push it to Blaxel's registry (private to your workspace)
3. Return an image ID you can use for creating sandboxes
Use `bl deploy` instead if you also want Blaxel to automatically create a first sandbox from this image, so you can test it:
```bash theme={null}
bl deploy
```
You can monitor the sandbox deployment with:
```bash theme={null}
bl get sandbox mytemplate --watch
```
You can safely delete the sandbox afterwards, and keep using the image for new sandboxes.
#### Blaxel SDK
The Blaxel SDK includes a declarative image builder that lets you define custom sandbox images directly in your code using a fluent, chainable API. Instead of writing Dockerfiles manually, you describe your image step by step, and the SDK generates the Dockerfile, builds the image, and deploys it as a sandbox.
This approach is ideal for dynamic image definitions, CI/CD pipelines, or when your image configuration depends on runtime logic.
The declarative image builder is available in the **TypeScript** and **Python** SDKs. The Go SDK does not support this feature.
Here is a simple example of how to use it:
```typescript TypeScript theme={null}
import { ImageInstance } from "@blaxel/core";
const image = ImageInstance.fromRegistry("python:3.11-slim")
.aptInstall("git", "curl")
.workdir("/app")
.pipInstall("requests", "httpx", "pydantic")
.env({ PYTHONUNBUFFERED: "1" });
const sandbox = await image.build({
name: "my-sandbox",
memory: 4096,
});
```
```python Python theme={null}
from blaxel.core import ImageInstance
image = (
ImageInstance.from_registry("python:3.11-slim")
.apt_install("git", "curl")
.workdir("/app")
.pip_install("requests", "httpx", "pydantic")
.env(PYTHONUNBUFFERED="1")
)
sandbox = await image.build(
name="my-sandbox",
memory=4096,
)
```
The three steps to use the image builder are:
1. **Define**: Use the fluent API to describe your image starting from a base image.
2. **Chain**: Each method returns a new immutable `ImageInstance`, so you can branch and reuse configurations.
3. **Build**: Call `build()` to generate a Dockerfile, package it with any local files, upload it to Blaxel, and deploy it as a sandbox.
The SDK automatically injects the Blaxel sandbox API binary and sets a default entrypoint (if you haven't specified one), so your image is ready to use as a sandbox without manual configuration.
##### Select a base image
Start by selecting a base image from any Docker registry:
```typescript TypeScript theme={null}
import { ImageInstance } from "@blaxel/core";
const image = ImageInstance.fromRegistry("ubuntu:22.04");
```
```python Python theme={null}
from blaxel.core import ImageInstance
image = ImageInstance.from_registry("ubuntu:22.04")
```
##### Execute build commands
Execute shell commands during the image build:
```typescript TypeScript theme={null}
const image = ImageInstance.fromRegistry("node:20")
.runCommands(
"apt-get update && apt-get install -y python3 make g++"
);
```
```python Python theme={null}
image = (
ImageInstance.from_registry("node:20")
.run_commands(
"apt-get update && apt-get install -y python3 make g++",
)
)
```
##### Use package managers
The SDK provides convenience methods for common package managers. These generate optimized `RUN` commands with sensible defaults (e.g., cache cleanup for apt).
###### Python (pip)
```typescript TypeScript theme={null}
// Simple install
const image = ImageInstance.fromRegistry("python:3.11-slim")
.pipInstall("requests", "numpy>=1.20", "pandas");
// With options
const image = ImageInstance.fromRegistry("python:3.11-slim")
.pipInstall(
{ indexUrl: "https://my-private-index.com/simple", pre: true },
"my-package",
"another-package"
);
```
```python Python theme={null}
# Simple install
image = (
ImageInstance.from_registry("python:3.11-slim")
.pip_install("requests", "numpy>=1.20", "pandas")
)
# With options
image = (
ImageInstance.from_registry("python:3.11-slim")
.pip_install(
"my-package",
"another-package",
index_url="https://my-private-index.com/simple",
pre=True,
)
)
```
###### Python (uv)
```typescript TypeScript theme={null}
const image = ImageInstance.fromRegistry("python:3.11-slim")
.runCommands("pip install uv")
.uvInstall("requests", "httpx");
```
```python Python theme={null}
image = (
ImageInstance.from_registry("python:3.11-slim")
.run_commands("pip install uv")
.uv_install("requests", "httpx")
)
```
###### Node.js (npm, yarn, pnpm, bun)
```typescript TypeScript theme={null}
// Install specific packages with npm (default)
const image = ImageInstance.fromRegistry("node:20-alpine")
.npmInstall("express", "typescript");
// Use a different package manager
const image = ImageInstance.fromRegistry("node:20-alpine")
.npmInstall(
{ packageManager: "pnpm", globalInstall: true },
"turbo"
);
// Install from package.json (no packages specified)
const image = ImageInstance.fromRegistry("node:20-alpine")
.workdir("/app")
.copy("package.json", "/app/package.json")
.npmInstall();
```
```python Python theme={null}
# Install specific packages with npm (default)
image = (
ImageInstance.from_registry("node:20-alpine")
.npm_install("express", "typescript")
)
# Use a different package manager
image = (
ImageInstance.from_registry("node:20-alpine")
.npm_install("turbo", package_manager="pnpm", global_install=True)
)
# Install from package.json (no packages specified)
image = (
ImageInstance.from_registry("node:20-alpine")
.workdir("/app")
.copy("package.json", "/app/package.json")
.npm_install()
)
```
###### Install system packages (apt, apk)
```typescript TypeScript theme={null}
// Debian/Ubuntu (apt-get)
const image = ImageInstance.fromRegistry("ubuntu:22.04")
.aptInstall("git", "curl", "build-essential");
// Alpine (apk)
const image = ImageInstance.fromRegistry("node:20-alpine")
.apkAdd("git", "curl", "python3");
```
```python Python theme={null}
# Debian/Ubuntu (apt-get)
image = (
ImageInstance.from_registry("ubuntu:22.04")
.apt_install("git", "curl", "build-essential")
)
# Alpine (apk)
image = (
ImageInstance.from_registry("node:20-alpine")
.apk_add("git", "curl", "python3")
)
```
###### Use other package managers
```typescript TypeScript theme={null}
// Ruby gems
const image = ImageInstance.fromRegistry("ruby:3.2")
.gemInstall("rails", "bundler");
// Rust crates
const image = ImageInstance.fromRegistry("rust:1.75")
.cargoInstall({ locked: true }, "cargo-watch", "cargo-edit");
// Go packages
const image = ImageInstance.fromRegistry("golang:1.22")
.goInstall("github.com/golangci/golangci-lint/cmd/golangci-lint@latest");
// PHP Composer
const image = ImageInstance.fromRegistry("php:8.3-cli")
.composerInstall("laravel/framework", "guzzlehttp/guzzle");
// Python CLI tools (pipx)
const image = ImageInstance.fromRegistry("python:3.11")
.pipxInstall("black", "ruff");
```
```python Python theme={null}
# Ruby gems
image = (
ImageInstance.from_registry("ruby:3.2")
.gem_install("rails", "bundler")
)
# Rust crates
image = (
ImageInstance.from_registry("rust:1.75")
.cargo_install("cargo-watch", "cargo-edit", locked=True)
)
# Go packages
image = (
ImageInstance.from_registry("golang:1.22")
.go_install("github.com/golangci/golangci-lint/cmd/golangci-lint@latest")
)
# PHP Composer
image = (
ImageInstance.from_registry("php:8.3-cli")
.composer_install("laravel/framework", "guzzlehttp/guzzle")
)
# Python CLI tools (pipx)
image = (
ImageInstance.from_registry("python:3.11")
.pipx_install("black", "ruff")
)
```
##### Add Dockerfile instructions
All standard Dockerfile instructions are available as methods.
###### Specify the working directory
```typescript TypeScript theme={null}
const image = ImageInstance.fromRegistry("node:20")
.workdir("/app");
```
```python Python theme={null}
image = ImageInstance.from_registry("node:20").workdir("/app")
```
###### Define environment variables
```typescript TypeScript theme={null}
const image = ImageInstance.fromRegistry("python:3.11")
.env({
PYTHONUNBUFFERED: "1",
APP_ENV: "production",
});
```
```python Python theme={null}
image = (
ImageInstance.from_registry("python:3.11")
.env(PYTHONUNBUFFERED="1", APP_ENV="production")
)
```
###### Copy files
```typescript TypeScript theme={null}
const image = ImageInstance.fromRegistry("node:20")
.workdir("/app")
.copy("package.json", "/app/package.json")
.copy("src", "/app/src");
```
```python Python theme={null}
image = (
ImageInstance.from_registry("node:20")
.workdir("/app")
.copy("package.json", "/app/package.json")
.copy("src", "/app/src")
)
```
###### Expose ports
```typescript TypeScript theme={null}
const image = ImageInstance.fromRegistry("node:20")
.expose(3000, 8080);
```
```python Python theme={null}
image = ImageInstance.from_registry("node:20").expose(3000, 8080)
```
###### Set an entrypoint
```typescript TypeScript theme={null}
const image = ImageInstance.fromRegistry("python:3.11")
.entrypoint("python", "-m", "uvicorn", "main:app");
```
```python Python theme={null}
image = (
ImageInstance.from_registry("python:3.11")
.entrypoint("python", "-m", "uvicorn", "main:app")
)
```
###### Add users
```typescript TypeScript theme={null}
const image = ImageInstance.fromRegistry("ubuntu:22.04")
.runCommands("useradd -m appuser")
.user("appuser");
```
```python Python theme={null}
image = (
ImageInstance.from_registry("ubuntu:22.04")
.run_commands("useradd -m appuser")
.user("appuser")
)
```
###### Add labels and build arguments
```typescript TypeScript theme={null}
const image = ImageInstance.fromRegistry("python:3.11")
.label({ version: "1.0", maintainer: "team@example.com" })
.arg("BUILD_ENV", "production");
```
```python Python theme={null}
image = (
ImageInstance.from_registry("python:3.11")
.label(version="1.0", maintainer="team@example.com")
.arg("BUILD_ENV", "production")
)
```
##### Add local files
Copy files and directories from your local machine into the image:
```typescript TypeScript theme={null}
const image = ImageInstance.fromRegistry("python:3.11")
.workdir("/app")
.addLocalFile("./config.json", "/app/config.json")
.addLocalDir("./src", "/app/src");
```
```python Python theme={null}
image = (
ImageInstance.from_registry("python:3.11")
.workdir("/app")
.add_local_file("./config.json", "/app/config.json")
.add_local_dir("./src", "/app/src")
)
```
Local files are resolved to absolute paths, included in the build context, and `COPY`-ed into the image. You can optionally provide a `contextName` (TypeScript) / `context_name` (Python) parameter to control the filename in the build context.
##### Build and deploy
###### Basic build
```typescript TypeScript theme={null}
const sandbox = await image.build({
name: "my-sandbox",
});
```
```python Python theme={null}
sandbox = await image.build(name="my-sandbox")
```
###### Build with all options
```typescript TypeScript theme={null}
const sandbox = await image.build({
name: "my-sandbox",
memory: 8192, // Memory in MB (default: 4096)
timeout: 900000, // Timeout in ms (default: 900000 = 15 min) — NOTE: Python SDK uses seconds
sandboxVersion: "latest", // Sandbox API version
onStatusChange: (status) => {
console.log(`Build status: ${status}`);
},
});
```
```python Python theme={null}
sandbox = await image.build(
name="my-sandbox",
memory=8192, # Memory in MB (default: 4096)
timeout=900.0, # Timeout in seconds (default: 900) — NOTE: TypeScript SDK uses ms
sandbox_version="latest", # Sandbox API version
on_status_change=lambda s: print(f"Build status: {s}"),
)
```
The `build()` method automatically:
* Injects the Blaxel sandbox API binary
* Sets a default entrypoint if none was specified
* Packages the Dockerfile and local files into a ZIP archive
* Uploads and deploys the image as a sandbox
* Runs the default [image slimming](#image-slimming) pass after the Docker build
* Polls until the sandbox reaches `DEPLOYED` or `FAILED` status
###### Inspect the generated Dockerfile
You can preview the Dockerfile without building:
```typescript TypeScript theme={null}
const image = ImageInstance.fromRegistry("python:3.11-slim")
.aptInstall("git")
.workdir("/app")
.pipInstall("requests");
console.log(image.dockerfile);
// FROM python:3.11-slim
// RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/*
// WORKDIR /app
// RUN pip install requests
```
```python Python theme={null}
image = (
ImageInstance.from_registry("python:3.11-slim")
.apt_install("git")
.workdir("/app")
.pip_install("requests")
)
print(image.dockerfile)
# FROM python:3.11-slim
# RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/*
# WORKDIR /app
# RUN pip install requests
```
###### Write to disk
You can also write the image to a folder for manual inspection or use with `bl deploy`:
```typescript TypeScript theme={null}
// Write to a specific directory
const buildDir = image.write("./output", "my-image");
// Write to a temporary directory
const tempDir = image.writeTemp();
```
```python Python theme={null}
# Write to a specific directory
build_dir = image.write("./output", "my-image")
# Write to a temporary directory
temp_dir = image.write_temp()
```
##### Understand immutability and branching
Each method returns a new `ImageInstance`, leaving the original unchanged. This lets you create base images and branch from them:
```typescript TypeScript theme={null}
// Define a shared base
const base = ImageInstance.fromRegistry("python:3.11-slim")
.aptInstall("git", "curl")
.workdir("/app");
// Branch for different use cases
const mlImage = base
.pipInstall("torch", "transformers", "datasets")
.env({ CUDA_VISIBLE_DEVICES: "0" });
const webImage = base
.pipInstall("fastapi", "uvicorn", "sqlalchemy")
.expose(8000)
.entrypoint("python", "-m", "uvicorn", "main:app", "--host", "::");
// Build both independently
const mlSandbox = await mlImage.build({ name: "ml-sandbox", memory: 16384 });
const webSandbox = await webImage.build({ name: "web-sandbox", memory: 4096 });
```
```python Python theme={null}
# Define a shared base
base = (
ImageInstance.from_registry("python:3.11-slim")
.apt_install("git", "curl")
.workdir("/app")
)
# Branch for different use cases
ml_image = (
base
.pip_install("torch", "transformers", "datasets")
.env(CUDA_VISIBLE_DEVICES="0")
)
web_image = (
base
.pip_install("fastapi", "uvicorn", "sqlalchemy")
.expose(8000)
.entrypoint("python", "-m", "uvicorn", "main:app", "--host", "::")
)
# Build both independently
ml_sandbox = await ml_image.build(name="ml-sandbox", memory=16384)
web_sandbox = await web_image.build(name="web-sandbox", memory=4096)
```
##### Example script
A complete example building a Node.js / Python development sandbox:
```typescript TypeScript theme={null}
import { ImageInstance } from "@blaxel/core";
const sandbox = await ImageInstance.fromRegistry("node:20-bookworm-slim")
// System dependencies
.aptInstall("git", "curl", "build-essential")
// Working directory
.workdir("/app")
// Node.js dependencies
.npmInstall({ packageManager: "npm", globalInstall: true }, "turbo")
// Environment
.env({
NODE_ENV: "development",
EDITOR: "code",
})
// Expose dev server ports
.expose(3000, 5173)
// Build and deploy
.build({
name: "node-dev",
memory: 8192,
onStatusChange: (status) => console.log(`Status: ${status}`),
});
console.log("Sandbox deployed:", sandbox.metadata?.name);
```
```python Python theme={null}
import asyncio
from blaxel.core import ImageInstance
async def main():
sandbox = await (
ImageInstance.from_registry("python:3.12-slim")
# System dependencies
.apt_install("git", "curl", "build-essential")
# Working directory
.workdir("/app")
# Python dependencies
.pip_install("httpie", "ipython", "requests", "fastapi", "uvicorn")
# Environment
.env(
PYTHONUNBUFFERED="1",
EDITOR="code",
)
# Expose dev server port
.expose(8000)
# Build and deploy
.build(
name="python-dev",
memory=8192,
on_status_change=lambda s: print(f"Status: {s}"),
)
)
print("Sandbox deployed:", sandbox.metadata.name if sandbox.metadata else None)
asyncio.run(main())
```
##### API reference
###### Methods
| Method | Description |
| - | - |
| `fromRegistry` / `from_registry` | Create an image from a Docker registry base image |
| `runCommands` / `run_commands` | Run shell commands (`RUN`) |
| `workdir` | Set working directory (`WORKDIR`) |
| `env` | Set environment variables (`ENV`) |
| `copy` | Copy from build context (`COPY`) |
| `addLocalFile` / `add_local_file` | Add a local file to build context and image |
| `addLocalDir` / `add_local_dir` | Add a local directory to build context and image |
| `expose` | Expose ports (`EXPOSE`) |
| `entrypoint` | Set container entrypoint (`ENTRYPOINT`) |
| `user` | Set the user (`USER`) |
| `label` | Add labels (`LABEL`) |
| `arg` | Define build arguments (`ARG`) |
| `pipInstall` / `pip_install` | Install Python packages with pip |
| `uvInstall` / `uv_install` | Install Python packages with uv |
| `pipxInstall` / `pipx_install` | Install Python CLI apps with pipx |
| `aptInstall` / `apt_install` | Install Debian/Ubuntu packages |
| `apkAdd` / `apk_add` | Install Alpine packages |
| `npmInstall` / `npm_install` | Install Node.js packages (npm/yarn/pnpm/bun) |
| `gemInstall` / `gem_install` | Install Ruby gems |
| `cargoInstall` / `cargo_install` | Install Rust crates |
| `goInstall` / `go_install` | Install Go packages |
| `composerInstall` / `composer_install` | Install PHP packages |
| `build` | Build and deploy as a sandbox |
| `write` | Write image to a folder |
| `writeTemp` / `write_temp` | Write image to a temporary folder |
###### Properties
| Property | Description |
| - | - |
| `dockerfile` | Generated Dockerfile content |
| `hash` | 12-character SHA256 hash of the image configuration |
| `baseImage` / `base_image` | Base image tag |
#### Blaxel API
Although less common, it is also possible to create a sandbox image and sandbox by directly interacting with the Blaxel API.
Ensure that you have the following:
* A [Blaxel API key](https://app.blaxel.ai/profile/security)
* The workspace name, found at the bottom left corner of the [Blaxel Console](https://app.blaxel.ai/) or via `bl workspaces`
##### 1. Create a ZIP archive
Create a directory with the following project contents:
* `Dockerfile` (required) - Defines your custom sandbox image and must include `sandbox-api`
* Any custom scripts (e.g., `entrypoint.sh` for initialization logic)
* Configuration files or data files as needed
* Additional dependencies or binaries your sandbox requires
Here is an example of the expected project structure:
```text theme={null}
mytemplate/
├── Dockerfile # Required - defines your image
└── entrypoint.sh # Optional - for custom initialization
```
* The Dockerfile is the heart of your image. It defines what will be available in your sandbox environment. [See an example](#2-customize-the-dockerfile).
* The `entrypoint.sh` script runs when a sandbox is created from this image. [See an example](#4-define-initialization).
The Dockerfile can reference and use any files included in the ZIP archive. Everything gets extracted and built together as a Docker image.
Once the files are ready, create a ZIP archive containing the files:
```bash theme={null}
(cd mytemplate && zip -r ../mytemplate.zip .)
```
##### 2. Create a sandbox resource
Set your Blaxel API key and workspace as environment variables:
```bash theme={null}
export BL_API_KEY=YOUR_API_KEY
export BL_WORKSPACE=YOUR_WORKSPACE_NAME
```
Make an HTTP POST request to create or update your sandbox resource. This will return an upload URL for the ZIP archive.
```bash theme={null}
curl -v -X POST "https://api.blaxel.ai/v0/sandboxes?upload=true" \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: $BL_WORKSPACE" \
-H "Content-Type: application/json" \
-d '{
"apiVersion": "blaxel.ai/v1alpha1",
"kind": "Sandbox",
"metadata": {
"name": "my-sandbox"
},
"spec": {
"runtime": {
"memory": 2048
},
"region": "us-pdx-1"
}
}'
```
Note the `upload=true` query parameter in the request, which indicates intent to upload custom code.
The Blaxel API returns a JSON response. The response contains an `x-blaxel-upload-url` response header, containing the target URL to use when uploading your image. The URL is in the response headers, not the JSON body.
Example response:
```http theme={null}
HTTP/2 200
x-blaxel-upload-url: https://controlplane-prod-build-sources...
```
Refer to the documentation on [sandbox configuration parameters](/Sandboxes/Overview#understand-sandbox-configuration) for more information on the body of the POST request.
##### 3. Upload ZIP archive
Use an HTTP PUT request to upload the ZIP file to the upload URL. Replace the placeholder URL in the command below with the value of the `x-blaxel-upload-url` response header received earlier.
```bash theme={null}
export UPLOAD_URL="https://controlplane-prod-build-sources..."
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/zip" \
--data-binary @mytemplate.zip
```
The upload is successful when you receive a `200 OK` status code.
##### 4. Monitor deployment status
After uploading, poll the sandbox status endpoint to track the build and deployment progress.
Make a GET request to `https://api.blaxel.ai/v0/sandboxes/`, where SANDBOX-NAME is the `metadata.name` specified in the initial POST request.
```bash theme={null}
watch -n 1 'curl -s -X GET https://api.blaxel.ai/v0/sandboxes/my-sandbox -H "Authorization: Bearer $BL_API_KEY" -H "X-Blaxel-Workspace: $BL_WORKSPACE" | jq -r ".status"'
```
The `status` field of the response will progress through these values:
| Status | Description |
| - | - |
| `UPLOADING` | Code archive is being uploaded |
| `BUILDING` | Docker image is being built |
| `DEPLOYING` | Container is being deployed to the cluster |
| `DEPLOYED` | Sandbox is ready to use |
| `FAILED` | Deployment failed (check build logs) |
See [sandbox deployment statuses](/Sandboxes/Overview#sandbox-deployment-statuses) for the full list of statuses a sandbox can report outside of this initial deployment flow.
Continue polling every 3-5 seconds until the status reaches `DEPLOYED` or `FAILED`.
A first sandbox from that image is automatically created on Blaxel once deployment succeeds. You can safely delete the sandbox and keep using the image for new sandboxes.
##### Example script
Here's a complete example script that performs all the steps above:
```bash Shell expandable theme={null}
#!/bin/bash
# Real deployment script that executes the documented API workflow
set -e
# Configuration
SANDBOX_NAME="my-custom-sandbox-$(date +%s)"
SOURCE_DIR="mytemplate"
ZIP_FILE="mytemplate.zip"
BASE_URL="https://api.blaxel.ai/v0"
# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Cleanup function
cleanup() {
if [ -f "$ZIP_FILE" ]; then
rm -f "$ZIP_FILE"
echo -e "\n${GREEN}✓ Cleaned up temporary files${NC}"
fi
}
# Set trap to cleanup on exit
trap cleanup EXIT
# Validate credentials
if [ -z "$BL_API_KEY" ]; then
echo -e "${RED}Error: BL_API_KEY not set${NC}"
echo "Get your API key from workspace settings and set it with:"
echo " export BL_API_KEY='your-api-key'"
exit 1
fi
if [ -z "$BL_WORKSPACE" ]; then
echo -e "${RED}Error: BL_WORKSPACE not set${NC}"
echo "Set your workspace name with:"
echo " export BL_WORKSPACE='your-workspace-name'"
exit 1
fi
# Check if source directory exists
if [ ! -d "$SOURCE_DIR" ]; then
echo -e "${RED}Error: $SOURCE_DIR directory not found${NC}"
exit 1
fi
# Create ZIP archive
echo -e "${BLUE}Creating ZIP archive from $SOURCE_DIR...${NC}"
cd "$SOURCE_DIR"
zip -q -r "../${ZIP_FILE}" .
cd ..
FILE_SIZE=$(wc -c < "$ZIP_FILE" | tr -d ' ')
echo -e "${GREEN}✓ ZIP archive created: $ZIP_FILE ($FILE_SIZE bytes)${NC}\n"
# Step 1: Create sandbox and get upload URL
echo -e "${BLUE}[1/4] Creating sandbox '$SANDBOX_NAME'...${NC}"
# Create temporary file for headers
HEADERS_FILE=$(mktemp)
CREATE_RESPONSE=$(curl -s -D "$HEADERS_FILE" -w "\n%{http_code}" -X POST "$BASE_URL/sandboxes?upload=true" \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: $BL_WORKSPACE" \
-H "Content-Type: application/json" \
-d '{
"apiVersion": "blaxel.ai/v1alpha1",
"kind": "Sandbox",
"metadata": {
"name": "'"$SANDBOX_NAME"'"
},
"spec": {
"runtime": {
"memory": 2048
},
"region": "us-pdx-1"
}
}')
HTTP_CODE=$(echo "$CREATE_RESPONSE" | tail -n 1)
RESPONSE_BODY=$(echo "$CREATE_RESPONSE" | sed '$d')
if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then
echo -e "${RED}✗ Failed to create sandbox (HTTP $HTTP_CODE)${NC}"
echo "$RESPONSE_BODY" | jq . 2>/dev/null || echo "$RESPONSE_BODY"
rm -f "$HEADERS_FILE"
exit 1
fi
# Extract upload URL from response headers
UPLOAD_URL=$(grep -i "x-blaxel-upload-url:" "$HEADERS_FILE" | cut -d' ' -f2- | tr -d '\r\n')
if [ -z "$UPLOAD_URL" ]; then
echo -e "${RED}✗ No upload URL received in response headers${NC}"
rm -f "$HEADERS_FILE"
exit 1
fi
rm -f "$HEADERS_FILE"
echo -e "${GREEN}✓ Sandbox created${NC}"
echo ""
# Step 2: Upload ZIP file
echo -e "${BLUE}[2/4] Uploading code archive ($FILE_SIZE bytes)...${NC}"
UPLOAD_RESPONSE=$(curl -s -w "%{http_code}" -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/zip" \
--data-binary "@$ZIP_FILE")
HTTP_CODE="${UPLOAD_RESPONSE: -3}"
if [ "$HTTP_CODE" != "200" ]; then
echo -e "${RED}✗ Upload failed with status $HTTP_CODE${NC}"
exit 1
fi
echo -e "${GREEN}✓ Upload completed${NC}"
echo ""
# Step 3: Monitor deployment status
echo -e "${BLUE}[3/4] Monitoring deployment status...${NC}"
MAX_WAIT=900 # 15 minutes
START_TIME=$(date +%s)
LAST_STATUS=""
while true; do
# Check timeout
CURRENT_TIME=$(date +%s)
ELAPSED=$((CURRENT_TIME - START_TIME))
if [ $ELAPSED -gt $MAX_WAIT ]; then
echo -e "${RED}✗ Deployment timed out after $MAX_WAIT seconds${NC}"
exit 1
fi
# Get current status
STATUS_RESPONSE=$(curl -s -X GET "$BASE_URL/sandboxes/$SANDBOX_NAME" \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: $BL_WORKSPACE")
STATUS=$(echo "$STATUS_RESPONSE" | jq -r '.status // empty')
if [ -z "$STATUS" ]; then
echo -e "${YELLOW}Warning: Could not get status, retrying...${NC}"
sleep 3
continue
fi
# Log status changes
if [ "$STATUS" != "$LAST_STATUS" ]; then
echo " Status: $STATUS"
LAST_STATUS=$STATUS
fi
# Check terminal states
if [ "$STATUS" = "DEPLOYED" ]; then
IMAGE=$(echo "$STATUS_RESPONSE" | jq -r '.spec.runtime.image // empty')
echo ""
echo -e "${GREEN}🎉 Deployment complete!${NC}"
echo "Sandbox: $SANDBOX_NAME"
echo "Image: $IMAGE"
echo ""
# Step 4: Show how to use it
echo -e "${BLUE}[4/4] How to use your sandbox:${NC}"
echo ""
echo "Run a command:"
echo " bl run sandbox $SANDBOX_NAME"
echo ""
echo "Get sandbox details:"
echo " bl get sandbox $SANDBOX_NAME"
echo ""
echo "View logs:"
echo " bl logs sandbox $SANDBOX_NAME"
echo ""
echo "Delete sandbox:"
echo " bl delete sandbox $SANDBOX_NAME"
echo ""
exit 0
elif [ "$STATUS" = "FAILED" ]; then
echo ""
echo -e "${RED}✗ Deployment failed${NC}"
echo ""
echo "Check the sandbox status for error details:"
echo " curl -s -X GET '$BASE_URL/sandboxes/$SANDBOX_NAME' \\"
echo " -H 'Authorization: Bearer \$BL_API_KEY' \\"
echo " -H 'X-Blaxel-Workspace: \$BL_WORKSPACE' | jq ."
exit 1
elif [ "$STATUS" = "DEACTIVATED" ] || [ "$STATUS" = "DEACTIVATING" ] || [ "$STATUS" = "DELETING" ]; then
echo ""
echo -e "${RED}✗ Unexpected status: $STATUS${NC}"
exit 1
fi
# Wait before next poll
sleep 3
done
```
```typescript TypeScript expandable theme={null}
import { SandboxInstance, settings } from "@blaxel/core";
import { execSync } from "node:child_process";
import { existsSync, readFileSync, statSync, unlinkSync } from "node:fs";
import { resolve } from "node:path";
const SANDBOX_NAME = `my-custom-sandbox-${Math.floor(Date.now() / 1000)}`;
const SOURCE_DIR = "mytemplate";
const ZIP_FILE = "mytemplate.zip";
async function poll() {
const maxWait = 900;
const startTime = Date.now();
let lastStatus: string | null = null;
while (true) {
if ((Date.now() - startTime) / 1000 > maxWait) {
console.log(`Deployment timed out after ${maxWait} seconds`);
process.exit(1);
}
try {
const sandbox = await SandboxInstance.get(SANDBOX_NAME);
const status = sandbox.status;
if (!status) {
console.log("Warning: Could not get status, retrying...");
await new Promise((r) => setTimeout(r, 3000));
continue;
}
if (status !== lastStatus) {
console.log(` Status: ${status}`);
lastStatus = status;
}
if (status === "DEPLOYED") {
const image = sandbox.spec?.runtime?.image;
console.log(
`\nDeployment complete!\nSandbox: ${SANDBOX_NAME}\nImage: ${image}\n`
);
return;
} else if (status === "FAILED") {
console.log(
`\nDeployment failed\n\nCheck build logs with:\n bl logs sandbox ${SANDBOX_NAME}`
);
process.exit(1);
} else if (
status === "DEACTIVATED" ||
status === "DEACTIVATING" ||
status === "DELETING"
) {
console.log(`\nUnexpected status: ${status}`);
process.exit(1);
}
} catch {
console.log("Warning: Could not get status, retrying...");
}
await new Promise((r) => setTimeout(r, 3000));
}
}
async function main() {
try {
if (!existsSync(SOURCE_DIR) || !statSync(SOURCE_DIR).isDirectory()) {
console.error(`Error: ${SOURCE_DIR} directory not found`);
process.exit(1);
}
console.log(`Creating ZIP archive from ${SOURCE_DIR}...`);
execSync(`zip -r ${resolve(ZIP_FILE)} .`, {
cwd: resolve(SOURCE_DIR),
stdio: "pipe",
});
const fileSize = statSync(ZIP_FILE).size;
console.log(`ZIP archive created: ${ZIP_FILE} (${fileSize} bytes)\n`);
console.log(`[1/4] Creating sandbox '${SANDBOX_NAME}'...`);
await settings.authenticate();
const createResponse = await fetch(
`${settings.baseUrl}/sandboxes?upload=true`,
{
method: "POST",
headers: {
...settings.headers,
"Content-Type": "application/json",
},
body: JSON.stringify({
metadata: { name: SANDBOX_NAME },
spec: {
runtime: { memory: 2048 },
region: "us-pdx-1",
},
}),
}
);
if (createResponse.status !== 200 && createResponse.status !== 201) {
console.error(
`Failed to create sandbox (HTTP ${createResponse.status})`
);
console.error(await createResponse.text());
process.exit(1);
}
const uploadUrl = createResponse.headers.get("x-blaxel-upload-url");
if (!uploadUrl) {
console.error("No upload URL received in response headers");
process.exit(1);
}
console.log("Sandbox created\n");
console.log(`[2/4] Uploading code archive (${fileSize} bytes)...`);
const uploadResponse = await fetch(uploadUrl, {
method: "PUT",
headers: { "Content-Type": "application/zip" },
body: readFileSync(ZIP_FILE),
});
if (uploadResponse.status !== 200) {
console.error(`Upload failed with status ${uploadResponse.status}`);
process.exit(1);
}
console.log("Upload completed\n");
console.log("[3/4] Monitoring deployment status...");
await poll();
console.log(`[4/4] How to use your sandbox:\n`);
console.log(`Run a command:\n bl run sandbox ${SANDBOX_NAME}\n`);
console.log(`Get sandbox details:\n bl get sandbox ${SANDBOX_NAME}\n`);
console.log(`View logs:\n bl logs sandbox ${SANDBOX_NAME}\n`);
console.log(`Delete sandbox:\n bl delete sandbox ${SANDBOX_NAME}\n`);
} finally {
if (existsSync(ZIP_FILE)) {
unlinkSync(ZIP_FILE);
console.log("Cleaned up temporary files");
}
}
}
main();
```
```python Python expandable theme={null}
import asyncio
import sys
import time
import zipfile
from pathlib import Path
import httpx
from blaxel.core import SandboxInstance, client
SANDBOX_NAME = f"my-custom-sandbox-{int(time.time())}"
SOURCE_DIR = "mytemplate"
ZIP_FILE = "mytemplate.zip"
try:
if not Path(SOURCE_DIR).is_dir():
print(f"Error: {SOURCE_DIR} directory not found")
sys.exit(1)
print(f"Creating ZIP archive from {SOURCE_DIR}...")
with zipfile.ZipFile(ZIP_FILE, "w", zipfile.ZIP_DEFLATED) as zf:
for file in Path(SOURCE_DIR).rglob("*"):
if file.is_file():
zf.write(file, file.relative_to(SOURCE_DIR))
file_size = Path(ZIP_FILE).stat().st_size
print(f"ZIP archive created: {ZIP_FILE} ({file_size} bytes)\n")
print(f"[1/4] Creating sandbox '{SANDBOX_NAME}'...")
httpx_client = client.get_httpx_client()
response = httpx_client.post(
"/sandboxes",
params={"upload": "true"},
json={
"metadata": {"name": SANDBOX_NAME},
"spec": {
"runtime": {"memory": 2048},
"region": "us-pdx-1",
},
},
timeout=30,
)
if response.status_code not in (200, 201):
print(f"Failed to create sandbox (HTTP {response.status_code})")
print(response.text)
sys.exit(1)
upload_url = response.headers.get("x-blaxel-upload-url")
if not upload_url:
print("No upload URL received in response headers")
sys.exit(1)
print("Sandbox created\n")
print(f"[2/4] Uploading code archive ({file_size} bytes)...")
with open(ZIP_FILE, "rb") as f:
upload_response = httpx.put(
upload_url,
content=f.read(),
headers={"Content-Type": "application/zip"},
timeout=300,
)
if upload_response.status_code != 200:
print(f"Upload failed with status {upload_response.status_code}")
sys.exit(1)
print("Upload completed\n")
print("[3/4] Monitoring deployment status...")
async def poll():
max_wait = 900
start_time = time.time()
last_status = None
while True:
if time.time() - start_time > max_wait:
print(f"Deployment timed out after {max_wait} seconds")
sys.exit(1)
try:
sandbox = await SandboxInstance.get(SANDBOX_NAME)
status = sandbox.status
except Exception:
print("Warning: Could not get status, retrying...")
await asyncio.sleep(3)
continue
if status is None:
print("Warning: Could not get status, retrying...")
await asyncio.sleep(3)
continue
if status != last_status:
print(f" Status: {status}")
last_status = status
if status == "DEPLOYED":
image = sandbox.spec.runtime.image if sandbox.spec and sandbox.spec.runtime else None
print(f"\nDeployment complete!\nSandbox: {SANDBOX_NAME}\nImage: {image}\n")
return
elif status == "FAILED":
print(f"\nDeployment failed\n\nCheck build logs with:\n bl logs sandbox {SANDBOX_NAME}")
sys.exit(1)
elif status in ("DEACTIVATED", "DEACTIVATING", "DELETING"):
print(f"\nUnexpected status: {status}")
sys.exit(1)
await asyncio.sleep(3)
asyncio.run(poll())
print(f"[4/4] How to use your sandbox:\n")
print(f"Run a command:\n bl run sandbox {SANDBOX_NAME}\n")
print(f"Get sandbox details:\n bl get sandbox {SANDBOX_NAME}\n")
print(f"View logs:\n bl logs sandbox {SANDBOX_NAME}\n")
print(f"Delete sandbox:\n bl delete sandbox {SANDBOX_NAME}\n")
finally:
if Path(ZIP_FILE).exists():
Path(ZIP_FILE).unlink()
print("Cleaned up temporary files")
```
The script accepts a source directory path containing the Dockerfile and related code and uses it to deploy a sandbox on Blaxel. It monitors the deployment status until completion and also cleans up temporary files.
To use this script, first export your API key and credentials as below:
```bash theme={null}
export BL_API_KEY=YOUR-API-KEY
export BL_WORKSPACE=YOUR-WORKSPACE-NAME
```
Create a sandbox directory named `mytemplate` with your custom Dockerfile and entrypoint script:
```bash theme={null}
mkdir -p mytemplate
```
Save and run the script as `deploy-sandbox.sh` (Shell), `index.ts` (TypeScript) or `main.py` (Python):
```bash Shell theme={null}
chmod +x deploy-sandbox.sh
./deploy-sandbox.sh
```
```typescript TypeScript theme={null}
node index.ts
```
```python Python theme={null}
python main.py
```
### Deploy from a private registry
Instead of building from source, you can point `blaxel.toml` directly at an image in a private registry:
```toml theme={null}
type = "sandbox"
name = "my-custom-sandbox"
image = "docker.io/myuser/my-sandbox:latest"
[runtime]
memory = 4096
```
The image must include the [sandbox API binary](#2-customize-the-dockerfile).
You can then push or deploy the image using `bl push` or `bl deploy` as usual.
To configure registry credentials, you have three options (listed in priority order):
1. Pass the credentials inline to `bl push` or `bl deploy` with the `--registry-cred` flag
```shell theme={null}
bl push --image docker.io/myuser/my-sandbox:latest --name my-custom-sandbox --registry-cred "docker.io=myuser:mypassword"
```
2. Pass the credentials from a different configuration file to `bl push` or `bl deploy` with the `--docker-config` flag
```shell theme={null}
bl push --image docker.io/myuser/my-sandbox:latest --name my-custom-sandbox --docker-config ~/.docker/config.json
```
3. Place a `.docker/config.json` in your project directory using the standard Docker config format (you can copy your existing `~/.docker/config.json`). The file will be automatically detected and used by `bl push` or `bl deploy`.
Add `.docker/config.json` to your `.gitignore` to avoid committing registry credentials to source control.
```json theme={null}
{
"auths": {
"docker.io": {
"auth": ""
}
}
}
```
The following registries are currently supported:
* Docker Hub
* GitHub Container Registry
* Google Artifact Registry
* Amazon ECR
* Any private registry which supports Basic auth
### Use a custom image
Once an image is successfully pushed, you can spawn new sandboxes instantly by using its image ID.
Use the following command to retrieve the image ID of the most recently pushed image:
```docker theme={null}
# Retrieve your IMAGE_ID
bl get image sandbox/mytemplate --latest
```
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
// Create a new sandbox
const sandbox = await SandboxInstance.create({
name: "my-sandbox-from-template",
image: "IMAGE_ID",
memory: 4096,
region: "us-pdx-1",
ports: [{ name: "nextjs-dev", target: 3000 }, { name: "another-api", target: 8888 }]
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.create({
"name": "my-sandbox-from-template",
"image": "IMAGE_ID",
"memory": 4096,
"region": "us-pdx-1",
"ports": [{ "name": "nextjs-dev", "target": 3000 }, { "name": "another-api", "target": 8888 }]
})
```
### Run the workload as a non-root user
Everything in a sandbox runs as `root` by default. To scope processes, terminals and filesystem operations to an unprivileged user while the sandbox API keeps the privileges it needs, declare the user in your Dockerfile and enable the feature:
```docker theme={null}
RUN adduser -D -u 10001 -h /blaxel app
USER app
ENV BL_SANDBOX_USER_ENABLED=true
```
See [running sandbox workloads as a non-root user](/Sandboxes/Non-root-user) for the entrypoint variant, what stays privileged, and the limitations.
### Update a custom image
To update an existing custom image:
1. Modify your Dockerfile or configuration
2. Rebuild locally to test changes
3. Push the new version:
```bash theme={null}
bl push
```
The new revision becomes available while the old one remains accessible.
## Image slimming
After the Docker build finishes, Blaxel runs a slimming pass on the image root filesystem. The pass reduces image size and sandbox boot time. It removes a fixed list of paths that are typically only needed while the image is building:
* package manager caches
* documentation and man pages
* headers and static libraries
* systemd
* Python `__pycache__` directories
* build toolchains, including `make`, `gcc`, `g++`, `cmake`, `autoconf`, and `automake`
The same paths are removed from every image. `RUN` instructions can still use these tools while the image is building. The tools are absent when a sandbox starts from the published image.
`bl push` and `bl deploy` run this pass. Local `docker build` and `make run` keep the filesystem Docker produced. A binary can exist in that local container and be missing from the sandbox. The SDK `build()` method uploads the image to the same pipeline, so the pass runs there too.
Slimming is enabled by default. For images you publish with `bl push` or `bl deploy`, disable it in `blaxel.toml` when the running sandbox needs a compiler or another build tool:
```toml theme={null}
[build]
slim = false
```
With `slim = false`, the image keeps caches, documentation, headers, and build tools. The image is larger. Sandboxes take longer to boot. Leave slimming enabled when those files are only needed to compile dependencies during the image build.
## Clean up unused images
Use the Blaxel Console to delete unused image tags in bulk and free image storage. This is useful after repeated builds or when you approach your total image-size quota. Check your account's limits on the [Quotas page](https://app.blaxel.ai/account/quotas).
Cleanup applies to the selected workspace, across resource types, not only sandbox images. Image tags referenced by existing resources are kept, including those used by sandboxes on standby. Unused tags can be deleted even when another tag of the same image is still in use.
Cleanup permanently deletes unused image tags and cannot be undone. An image pushed with `bl push` but not referenced by a resource can be deleted, even if you plan to use it later. Review any images or old tags you want to keep for future deployments before confirming.
To clean up images:
1. Open the [Blaxel Console](https://app.blaxel.ai) and select the workspace you want to clean up.
2. In the sidebar, open Sandboxes > Images. Review the images and their Total size values.
3. Click Cleanup Unused to open the Cleanup Unused Images dialog.
4. Review the warning and type `cleanup` in the confirmation field.
5. Click Cleanup Images to delete unused image tags.
The image list refreshes after cleanup. If image storage is still near your quota, review the remaining images and their tags to identify what you still need. Cleanup does not delete your sandboxes or other resources.
## Share images across workspaces and accounts
Once you have a custom image, you can share it with other workspaces in the same [account](/Security/Workspace-access-control), or with other accounts - for example, to promote an image from `development` to `production` without rebuilding or re-pushing, or to share an image between different teams. Only the metadata record is copied to the target workspace or account; the underlying storage stays in the source.
When you share an image:
1. The metadata is copied to the target workspace or account, pointing to the same underlying data in the source.
2. The target workspace or account can use the shared image to create sandboxes, just like a locally-owned image.
3. Storage billing stays with the source workspace or account.
4. New tags pushed to the source image are automatically propagated to all workspaces and accounts it is shared with.
Shared images in the consuming workspace or account include a `sourceWorkspace` field. In the [Blaxel Console](https://app.blaxel.ai), they are marked with a badge showing the source workspace or account name.
### Prerequisites
* When sharing between workspaces or accounts, you must be an **administrator** of the source workspace. When sharing between accounts, your share request will be completed only when it receives approval from an administrator of the target account.
* The image must be **locally owned** in the source workspace or account. You cannot re-share an image that was shared to you from elsewhere.
### Share an image
#### Blaxel Console
To share with another workspace in the same account:
1. Navigate to the **Images** page in the source workspace.
2. Click the actions menu (three dots) on the image you want to share.
3. Select **Share**.
4. In the dialog, select the **Same account** tab.
5. Select the target workspace from the dropdown.
6. Click **Share** to confirm.
The shared image appears immediately in the target workspace's image list.
To share with another account:
1. Navigate to the **Images** page in the source workspace.
2. Click the actions menu (three dots) on the image you want to share.
3. Select **Share**.
4. In the dialog, select the **Different account** tab.
5. Select the target account from the dropdown.
6. Click **Share** to confirm.
The share request must be approved by an administrator of the target account.
#### Blaxel CLI
Currently, the Blaxel CLI only supports image sharing across workspaces. To share images across accounts, use the Blaxel Console or the Management API instead.
Use the [`bl share image`](/cli-reference/commands/bl_share_image) command:
```bash theme={null}
bl share image sandbox/ --workspace
```
Sharing applies to the entire image (all tags). Tag-qualified references like `sandbox/my-template:v1.0` are not accepted.
#### Management API
For sharing between workspaces in the same account:
```bash theme={null}
curl -X POST "https://api.blaxel.ai/v0/images/sandbox/{imageName}/share" \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: $BL_WORKSPACE" \
-H "Content-Type: application/json" \
-d '{"targetWorkspace": "production"}'
```
For sharing between workspaces in different accounts:
```bash theme={null}
curl -X POST "https://api.blaxel.ai/v0/images/sandbox/{imageName}/share" \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: $SOURCE_WORKSPACE" \
-H "Content-Type: application/json" \
-d '{
"targetWorkspace": "production",
"targetAccountId": "TARGET-ACCOUNT-ID"
}'
```
The target account ID can be obtained from the **Workspace settings** page of the Blaxel Console.
### List shared images
#### Blaxel Console
On the image detail page in the [Blaxel Console](https://app.blaxel.ai), images shared across workspaces or accounts are listed with the option to revoke sharing for each workspace or account individually.
#### Management API
```bash theme={null}
curl -X GET "https://api.blaxel.ai/v0/images/sandbox/{imageName}/share" \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: $BL_WORKSPACE"
```
### Unshare an image
Unsharing removes the metadata record from the target workspace or account. The image data in the source workspace is not affected.
You cannot delete an image (or individual tags) while it is shared. You must unshare from all connected workspaces or accounts first.
After unsharing, any deployments in the target workspace or account that reference the shared image will fail on their next restart or scale-up. Make sure no active deployments depend on the image before unsharing.
#### Blaxel Console
1. Navigate to the **Images** page in the source workspace.
2. Open the image detail page.
3. In the shared workspaces list, click **Unshare** next to the target workspace or account you want to revoke.
#### Blaxel CLI
Use the [`bl unshare image`](/cli-reference/commands/bl_unshare_image) command:
```bash theme={null}
bl unshare image sandbox/ --workspace
```
#### Management API
```bash theme={null}
curl -X DELETE "https://api.blaxel.ai/v0/images/sandbox/{imageName}/share/{targetWorkspace}" \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: $BL_WORKSPACE"
```
### Billing
Storage billing is tied to the source workspace. Metering and costs remain with the workspace that originally pushed the image, regardless of how many workspaces or accounts it is shared with.
The consuming workspace or account pays nothing for image storage of shared images.
### Constraints and limitations
* **No re-sharing**: A shared image cannot be re-shared from the consuming workspace to a third workspace or account. Only the original owner can share.
* **Deletion protection**: You cannot delete an image (or individual tags) while it is shared with other workspaces or accounts. You must unshare from all target workspaces first.
* **Whole image only**: Sharing applies to the entire image with all its tags. You cannot share individual tags.
* **Admin-only**: Both sharing and unsharing require administrator permissions.
## Best practices
### 1. Optimize for cold starts
* Use smaller base images (Alpine when possible)
* Minimize layers in Dockerfile
* Pre-install only essential packages
* Defer optional installations to runtime
* Leave [image slimming](#image-slimming) enabled unless the sandbox needs build tools at runtime
### 2. Cache dependencies
```docker theme={null}
# Good: Cache package installations
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# Bad: Invalidates cache on any file change
COPY . .
RUN npm install
```
### 3. Security considerations
* Don’t include secrets in images
* Use Blaxel’s secrets management for sensitive data
* Keep base images updated
* Scan for vulnerabilities regularly
### 4. Resource optimization
Choose appropriate resources for your use case:
| Use Case | Memory |
| - | - |
| Light development | 2GB |
| Small web application | 4GB |
| Full-stack web | 8GB |
Pre-populate volumes with files for faster environment setup.
Understand accounts, workspaces, and admin permissions.
# Sandbox variables and secrets
Source: https://docs.blaxel.ai/Sandboxes/Variables-and-secrets
Configure environment variables and secrets in Blaxel sandboxes to securely manage API keys, credentials, and runtime configuration values.
## Environment variables
There are multiple ways to configure environment variables in Blaxel sandboxes.
On the new sandbox generation, you can update environment variables on a running sandbox, or give them a new value when you [fork a sandbox](/Sandboxes/Fork#fork-with-new-environment-variables). Both are in preview. Other parts of the runtime spec, such as memory, cannot be changed on a running sandbox.
### When building new sandbox images
Environment variables defined in the sandbox image with the `ENV` Dockerfile directive are available in every sandbox created from it.
```dockerfile theme={null}
FROM blaxel/base-image:latest
ENV NODE_ENV=production
ENV PORT=3000
```
Never add secrets in a Dockerfile.
This also applies when creating sandbox images with the [declarative image builder](/Sandboxes/Templates#blaxel-sdk):
```typescript TypeScript theme={null}
import { ImageInstance } from "@blaxel/core";
const sandbox = await ImageInstance.fromRegistry("node:20-alpine")
.workdir("/app")
.env({
NODE_ENV: "production",
PORT: "3000",
})
.build({ name: "my-sandbox", memory: 4096 });
```
```python Python theme={null}
from blaxel.core import ImageInstance
sandbox = await (
ImageInstance.from_registry("node:20-alpine")
.workdir("/app")
.env(NODE_ENV="production", PORT="3000")
.build(name="my-sandbox", memory=4096)
)
```
### When instantiating sandboxes from existing images
When instantiating sandboxes from existing images, any environment variables defined in the base image are automatically available. Additional environment variables can be set using the following methods.
#### Set variables at sandbox creation time
Pass `envs` as an array of name/value objects when creating a sandbox with the Blaxel SDKs. These are set as environment variables and are available to every process running inside the deployed sandbox by default (although they can be overridden by process-level variables, discussed in the next section).
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.createIfNotExists({
name: "my-sandbox",
image: "blaxel/base-image:latest",
region: "us-pdx-1",
envs: [
{ name: "NODE_ENV", value: "production" },
{ name: "PORT", value: "3000" },
],
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.create_if_not_exists({
"name": "my-sandbox",
"image": "blaxel/base-image:latest",
"region": "us-pdx-1",
"envs": [
{ "name": "NODE_ENV", "value": "production" },
{ "name": "PORT", "value": "3000" },
],
})
```
#### Update variables on an existing sandbox
Read the sandbox, change its `envs` list, and update it. A variable you drop from the list is removed, and a variable you add or change takes the new value.
```typescript TypeScript theme={null}
import { getSandbox, updateSandbox } from "@blaxel/core";
const { data: sandbox } = await getSandbox({
path: { sandboxName: "my-sandbox" },
throwOnError: true,
});
sandbox.spec.runtime.envs = [
{ name: "NODE_ENV", value: "staging" },
{ name: "PORT", value: "3000" },
];
await updateSandbox({
path: { sandboxName: "my-sandbox" },
body: sandbox,
throwOnError: true,
});
```
```python Python theme={null}
from blaxel.core.client.api.compute.get_sandbox import asyncio as get_sandbox
from blaxel.core.client.api.compute.update_sandbox import asyncio as update_sandbox
from blaxel.core.client.client import client
from blaxel.core.client.models.env import Env
sandbox = await get_sandbox("my-sandbox", client=client)
sandbox.spec.runtime.envs = [
Env(name="NODE_ENV", value="staging"),
Env(name="PORT", value="3000"),
]
await update_sandbox("my-sandbox", client=client, body=sandbox)
```
```bash HTTP API theme={null}
curl -X PUT https://api.blaxel.ai/v0/sandboxes/my-sandbox \
-H "Authorization: Bearer $BL_API_KEY" \
-H "X-Blaxel-Workspace: my-workspace" \
-H "Content-Type: application/json" \
-d @sandbox.json
```
The update replaces the whole sandbox definition, so send back the sandbox you read with only its `envs` changed. When the variables are the only change, the new environment reaches the sandbox while it keeps running: processes you start afterwards read the new values, and processes already running keep the environment they were started with. Change anything else at the same time, memory for example, and the sandbox is redeployed instead, which restarts it and loses its state.
To keep the state of a sandbox and run it with a different environment, [fork it with new variables](/Sandboxes/Fork#fork-with-new-environment-variables) instead.
### When executing sandbox processes
Environment variables can also be set at process execution, applicable only to that specific process.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
const process = await sandbox.process.exec({
command: "node server.js",
env: {
PORT: "8080",
LOG_LEVEL: "debug",
},
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.get("my-sandbox")
process = await sandbox.process.exec({
"command": "node server.js",
"env": {
"PORT": "8080",
"LOG_LEVEL": "debug",
},
})
```
## Secrets
### Use proxy injection (recommended)
The recommended way to inject secrets into a sandbox is with the Blaxel proxy. This intercepts outbound HTTPS requests from the sandbox and injects secrets server-side using `{{SECRET:name}}` placeholders. The sandbox code never sees raw API keys or credentials.
See the [proxy routing with secrets injection](/Sandboxes/Proxy-secrets-injection) documentation for examples.
### Use a .env.build file
Build variables let you pass secrets and configuration values into the Docker build phase without exposing them at runtime. This is useful when your build process needs credentials that should never appear inside the deployed sandbox.
Create a `.env.build` file in the root of your project for build secrets. A common example of this is installing private npm packages, which require an `NPM_TOKEN` during `npm install`. Variables defined here are injected during the build phase only and are never persisted in the runtime environment.
```bash .env.build theme={null}
MY_SECRET_BUILD_VAR=I_AM_A_SECRET
```
Use the `--build-env-file` argument to `bl deploy` to specify a custom file name or path instead of the default `.env.build`.
Ensure that `.env.build` is ignored during commits to avoid accidentally making secrets public.
# Volumes for sandboxes
Source: https://docs.blaxel.ai/Sandboxes/Volumes
Attach Blaxel Volumes to sandboxes for persistent storage that survives sandbox destruction and recreation, with mount path configuration.
Blaxel Volumes provide persistent storage that survives sandbox destruction and recreation.
For full documentation on creating, deleting, resizing, and listing volumes, see the [Volumes overview](/Volumes/Overview).
## Attach a volume to a sandbox
At this time, you can only attach one volume to a sandbox. A volume can also only be attached to one sandbox at a time.
To use a volume, attach it **when you create a sandbox** by passing an array of volume configurations to the `volumes` property.
Each configuration must include the `name` of the volume to attach and the `mountPath` where it will be accessible inside the sandbox's filesystem. The mount path **will override the existing content** of a directory.
Use `createIfNotExists()` / `create_if_not_exists()` so that re-running your setup code is safe even if the sandbox already exists with the volume attached:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.createIfNotExists({
name: "my-sandbox",
image: "blaxel/nextjs:latest",
memory: 4096,
region: "us-pdx-1",
volumes: [{
name: "my-volume", // Must match the name of the created volume
mountPath: "/app", // Directory inside the sandbox
readOnly: false // Set to true to prevent writes
}],
// ... other sandbox properties
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.create_if_not_exists({
"name": "my-sandbox",
"image": "blaxel/nextjs:latest",
"memory": 4096,
"region": "us-pdx-1",
"volumes": [{
"name": "my-volume", # Must match the name of the created volume
"mount_path": "/app", # Directory inside the sandbox
"read_only": False # Set to True to prevent writes
}],
# ... other sandbox properties
})
```
Any files written to the `/app` directory within this sandbox will be stored on `my-volume` and will persist even if this sandbox is deleted.
At this time, you cannot detach a volume from a sandbox.
## Check volume attachment status
Each volume returned from the API includes a `state.attachedTo` field indicating which sandbox the volume is currently attached to.
When a sandbox is deleted, Blaxel automatically clears this field on the volume. You can therefore check `state.attachedTo` directly to determine whether a volume is free to attach to a new sandbox.
Learn how to create and manage volumes.
Pre-populate volumes with files for faster environment setup.
# Sandbox best practices
Source: https://docs.blaxel.ai/Sandboxes/best-practices
Recommended practices for sandbox lifecycle management, scale-to-zero behavior, persistent storage, agent architecture, and MCP server design.
Unlike traditional sandbox providers, Blaxel Sandboxes automatically scale up and down at near-instant speeds, and persist forever in standby. As such, here are some recommended best practices so you can best make use of the platform features.
## Use the sandbox lifecycle strategically
**Treat sandboxes as persistent computers**, rather than thinking of them as ephemeral runtimes that must be wiped clean after every interaction. Just as your laptop isn't reformatted every time you close the lid, you shouldn't destroy a sandbox's state simply because a session ended. Blaxel Sandboxes are designed to maintain persistent storage and system state, allowing agents to retain context, shell history, and installed dependencies indefinitely.
The most efficient way to manage lifecycle is to let the sandbox suspend automatically when idle, rather than explicitly destroying it. Auto-suspend happens automatically after 15s of inactivity. Resuming a standby sandbox is orders of magnitude faster than cold-booting a new box and re-running setup scripts (like `npm install` or `apt-get`). By relying on suspension, you ensure that when an agent returns (whether in ten minutes or ten weeks) the environment is restored exactly as it was left, enabling a seamless instant resume experience that saves both time and compute costs.
The definition of a session is at your discretion. It's a tradeoff between instant resume times from standby mode (\~25ms) and paying for the [standby snapshot storage cost](https://blaxel.ai/pricing). As a rule of thumb, most customers keep sandboxes in standby for 7-60 days -- see down below.
## Choose the right storage
Choosing the right storage for your sandbox data is important, as it can impact your agent's performance, reliability, and overall consistency. Blaxel offers three options:
* Sandbox filesystem: The sandbox itself has a stateful [filesystem](/Sandboxes/Filesystem) optimized for speed. Data stored here persists for as long as the sandbox exists in standby, but is erased when the sandbox is terminated.
* Volumes: [Volumes](/Volumes/Overview) provide longer-term, durable block storage that is decoupled from compute. Data stored on a volume survives even if the associated sandbox is terminated, and newly-created sandboxes can access the data by attaching to the same volume.
However, relying solely on volumes for state management comes with a trade-off: you lose the "instant resume" benefit of the sandbox filesystem and will incur a cold-boot penalty when creating a new sandbox and attaching an existing volume to it.
* Agent Drive: [Agent Drive](/Agent-drive/Overview) behaves like a shared, distributed filesystem that can be mounted to multiple sandboxes or agents at any time. Drives are highly optimized for small and medium-sized file operations.
Agent Drive uses a fine-grained replication strategy and it is also separately backed up. Drives scale automatically with no fixed capacity limits. Pre-provisioning or run-time resizing is not required.
Here's a table to help you decide:
| | Sandbox Filesystem | Volume | Agent Drive |
| - | - | - | - |
| Persistence | Until sandbox deletion | Survives sandbox deletion | Survives sandbox deletion |
| Durability | Low (lost on sandbox deletion or crash) | High (block storage, replicated) | High (distributed storage, replicated) |
| Access type | Single | Single | Multiple |
| Hot-attach | N/A | No | Yes |
| Interface | POSIX | POSIX | POSIX + S3 |
| Size | \~50% of sandbox memory by default; configurable with root storage | User-defined at creation, expandable | No size limit |
| Optimized for | Data that doesn’t require high durability | Data that requires durability with high performance | Shared data |
Refer to our [blog post on choosing the right storage for your Blaxel agents](https://blaxel.ai/blog/choose-the-right-storage-for-your-blaxel-agents) for more information.
There is no built-in way to bulk-sync files between a sandbox and your local machine. Instead, use [`sandbox.fs.download()`](/Sandboxes/Filesystem#download-file-to-host) for single files, or the zip-upload-unzip pattern in the next section for bulk transfers.
## Upload many files efficiently
Writing files one by one with `write()` costs one network round trip per file: at \~100ms per call, restoring several hundred files sequentially is minutes of pure latency.
For bulk transfers, bundle the files into a single zip archive, upload it with one `writeBinary()` (TypeScript) / `write_binary()` (Python) call, and extract it inside the sandbox. This turns N round trips into 2 calls, however many files you have.
```typescript TypeScript theme={null}
import AdmZip from "adm-zip";
const zip = new AdmZip();
for (const file of files) {
zip.addFile(file.path, Buffer.from(file.content));
}
await sandbox.fs.writeBinary("/tmp/project.zip", zip.toBuffer());
await sandbox.process.exec({
name: "unzip",
command: "unzip -o /tmp/project.zip -d /blaxel/app && rm /tmp/project.zip",
waitForCompletion: true,
});
```
```python Python theme={null}
import io, zipfile
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
for file in files:
zf.writestr(file["path"], file["content"])
await sandbox.fs.write_binary("/tmp/project.zip", buf.getvalue())
await sandbox.process.exec({
"name": "unzip",
"command": "unzip -o /tmp/project.zip -d /blaxel/app && rm /tmp/project.zip",
"wait_for_completion": True,
})
```
The TypeScript example uses the `adm-zip` package; any zip library works. The extraction step requires an `unzip` binary in the sandbox image. If your image lacks one, use `python3 -m zipfile -e /tmp/project.zip /blaxel/app` instead (Python is available in Blaxel Hub images), or install `unzip` in your template.
For small file sets, `writeTree()` (TypeScript) / `write_tree()` (Python) in the [filesystem API](/Sandboxes/Filesystem) writes multiple files in one batched call and is simpler. Use the zip pattern when the file count is large enough that per-call latency dominates.
If the resulting zip archive exceeds 5 MB, `writeBinary()` / `write_binary()` automatically splits it into multipart uploads. Refer to [the sandbox filesystem documentation](/Sandboxes/Filesystem#write-binary-file) for details.
## Automate cleanup with TTLs
In production, you will inevitably accumulate a long tail of sandboxes that will never be called upon again (completed tasks, abandoned user sessions, etc.). These can unnecessarily consume storage and add to your cost.
Instead of writing scripts to manually filter and delete sandboxes, use [expiration parameters and lifecycle policies](../Sandboxes/Expiration) to automate when a sandbox should be retired. You can configure this based on idle duration (*e.g., delete if it hasn't been active for 7 days*) or absolute maximum age (*e.g., delete after 30 days*).
In [quota tiers 0 and 1](https://app.blaxel.ai/account/quotas), a maximum TTL is enforced on all sandboxes. On quota tier 2 and above, no expiration policy is set by default.
The sandbox metadata also includes a read-only parameter, `expiresIn`, that computes the number of seconds until a sandbox is terminated due to its TTL or lifecycle policy.
## Use pagination for sandbox listings
Requesting a list of all sandboxes in a single API call forces the platform to scan the full workspace, which is slow and resource-intensive at scale. Use the `cursor` and `limit` parameters to [retrieve sandboxes in pages](/Sandboxes/Overview#list-sandboxes) instead. For more information and examples, refer to the [API](/api-reference/introduction#pagination) and [SDK](/sdk-reference/introduction#pagination) reference documentation.
## Index sandboxes with external IDs
When you manage many sandboxes per workspace (hundreds or thousands), retrieving the right one by name alone becomes impractical. You often need to find a sandbox by a caller-owned identifier: a session ID, user ID, or conversation ID from your application.
Use `externalId` to create a fast, indexed lookup without maintaining a separate mapping database. Unlike labels (which require client-side filtering), `externalId` is backed by a dedicated database index and returns results in constant time.
Common patterns:
* **One sandbox per user session**: Tag each sandbox with your session ID. When the user returns, call `getByExternalId` to reconnect to the same environment instantly.
* **Idempotent provisioning**: Before creating a new sandbox, check if one already exists for your workflow ID. This avoids duplicate sandbox creation in retry scenarios.
* **Multi-tenant isolation**: Tag sandboxes with tenant or user IDs for quick per-tenant enumeration via `list({ externalId })`.
```typescript TypeScript theme={null}
// Reconnect to the user's sandbox, or create one if none exists
let sandbox = await SandboxInstance.getByExternalId(sessionId);
if (!sandbox) {
sandbox = await SandboxInstance.createIfNotExists({
name: uniqueName(),
image: "blaxel/base-image:latest",
externalId: sessionId,
region: "us-pdx-1"
});
}
```
```python Python theme={null}
# Reconnect to the user's sandbox, or create one if none exists
sandbox = await SandboxInstance.get_by_external_id(session_id)
if not sandbox:
sandbox = await SandboxInstance.create_if_not_exists({
"name": unique_name(),
"image": "blaxel/base-image:latest",
"external_id": session_id,
"region": "us-pdx-1"
})
```
`externalId` is not unique. If multiple live sandboxes share the same value, `getByExternalId` returns the most recently created one. Use `list` with the `externalId` filter to retrieve all matches.
Combine external IDs with [TTL policies](/Sandboxes/Expiration) and [labels](/Sandboxes/Overview#labels) for a complete sandbox management strategy: external IDs for fast lookup, labels for categorization, and TTLs for automatic cleanup.
## Reduce cold start times with a pre-warmed pool
For workloads with heavy images, you can maintain a pool of pre-warmed sandboxes to serve requests with near-zero latency.
The pattern works as follows:
1. Keep a pool of sandboxes ready, with their data recorded in a database or cache.
2. When a task arrives, assign one sandbox from the pool to that task and mark it as in-use.
3. After assigning a sandbox, create a replacement in the background to keep the pool at your target size.
4. When the task finishes, either release the sandbox back to the pool or delete it and let the background replenishment fill the gap.
If using this pattern, implement a queuing system to coordinate sandbox usage and flag which sandboxes are in use. Without this, the same sandbox could be assigned to two tasks arriving at the same instant.
## Use idempotent creation for sandboxes and preview URLs
When creating sandboxes and preview URLs, use `createIfNotExists()` (TypeScript) / `create_if_not_exists()` (Python) instead of `create()`.
These methods are idempotent and handle conflicts gracefully when the requested resource already exists. If it exists, they return it; if not, they create it.
You should use `create()` only if you explicitly want an error to be raised when a sandbox or preview URL with the specified name already exists.
## Reconnect external connections after standby
When transitioning to standby mode, Blaxel automatically creates a snapshot of the entire state (including the complete file system in memory, preserving both files and running processes).
However, external network connections that were active at the time of transitioning to standby, such as database connections, message queues, and HTTP connection pools, are not preserved; the remote side will automatically time out and close. When the sandbox resumes, the sandbox side of the connection will be stale, and queries or commands using it will fail.
The recommended approach in this case is to use the connection library's built-in health-check or reconnection option to check or reset a connection before using it. For example, you could configure `pool_pre_ping` (SQLAlchemy) for PostgreSQL/MySQL connections, or `retryStrategy` (ioredis) for Redis connections.
## Retry strategically in case of errors
Blaxel returns [structured error responses](/troubleshooting/error-codes) in case of sandbox creation or connection failures. To handle these errors, we recommend the following strategy:
1. First, check the origin of the error.
Look at `error.origin` (or the `X-Blaxel-Source` header). If it is not `platform`, the 404 came from the application itself (e.g. a missing file), not from Blaxel. You should not re-create in that case.
2. Then, branch on `error.code`:
* `WORKLOAD_UNAVAILABLE`: The sandbox record exists but no healthy replica is currently serving it. This is the "sandbox is down" signal. It's marked `retryable: true`, so retry with exponential backoff (500ms to 30s, give up after \~60s). If retries keep failing, treat it as gone and re-create.
* `WORKLOAD_NOT_FOUND`: The sandbox record itself doesn't exist in the platform (deleted, never created, or wrong name). Definitely re-create. Not retryable.
* `ROUTE_NOT_FOUND`: The URL is wrong. This is a URL construction bug, not a sandbox availability issue. Don't re-create, fix the URL. The SDK's `sandbox.metadata.url` is the canonical source.
For more information, refer to the [list of error codes](/troubleshooting/error-codes).
# Access tokens
Source: https://docs.blaxel.ai/Security/Access-tokens
Authenticate to Blaxel APIs and the CLI using personal access tokens, with guidance on creating, scoping, rotating, and revoking tokens securely.
User access tokens can be used in order to authenticate to Blaxel by API or CLI. They apply for both [users](/Security/Workspace-access-control) and [service accounts](/Security/Service-accounts). They are generated through a variety of methods, which are documented below.
## Overview of authentication methods on Blaxel
Blaxel employs two main authentication paradigms: **short-lived tokens** (OAuth) and **long-lived tokens** (API keys).
Long-lived tokens are easier to use but are less secure as their validity can go from multiple days to infinite. They are generated as **API keys** from the Blaxel console.
**OAuth tokens** are recommended for security reasons, as their duration is only 2 hours (short-lived). They are generated through [OAuth 2.0](https://oauth.net/2/) authentication endpoints.
A personal API key or personal OAuth login grants access to every [workspace](/Security/Workspace-access-control) your account belongs to, while a service account's API key or client-credentials token only grants access to the single workspace that service account belongs to. If a request fails with a 403 error even though the credential is valid, this usually means the credential doesn't have access to the specified workspace.
## API keys
Long-lived authentication tokens are called **API keys** on Blaxel. Their validity duration can be infinite.
API keys can be used in the Blaxel APIs, CLI and SDK.
**To authenticate in the SDKs**, use the following two variables **`BL_WORKSPACE`** and **`BL_API_KEY`**, which can be set in one of these sources:
1. your **`.env`** file
2. your machine's environment variables
3. the local configuration file created when you log in through the CLI (see below)
**To authenticate in the CLI**, use this command:
```bash theme={null}
export BL_API_KEY=YOUR-API-KEY
bl login YOUR-WORKSPACE
```
**To authenticate in the APIs**, use the API key as an authorization bearer in the headers `Authorization` or `X-Blaxel-Authorization` in any call to the Blaxel APIs. For example, to list models:
```bash theme={null}
curl 'https://api.blaxel.ai/v0/models' \
-H 'Accept: application/json, text/plain, */*' \
-H 'Authorization: Bearer YOUR-API-KEY'
```
### Use API keys in CI
#### GitHub Action
The easiest way to integrate the Blaxel CLI into a CI pipeline is with the official [Blaxel GitHub Action](https://github.com/blaxel-ai/bl-action).
To use this, add a new secret to your GitHub repository named `BL_API_KEY` and set it to the value of your Blaxel API key. You can then use the GitHub Action in a GitHub workflow, as shown below:
```yaml theme={null}
name: Deploy
on:
push:
branches:
- "main"
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Deploy to Blaxel
uses: blaxel-ai/bl-action@v1
with:
workspace: "YOUR_WORKSPACE"
apikey: ${{ secrets.BL_API_KEY }}
- name: Deploy to Blaxel
run: bl deploy
```
#### Manual configuration
If you prefer to set up authentication manually or are using a different CI platform, follow these steps:
* Set the API key as an environment variable in your CI environment:
```shell theme={null}
export BL_API_KEY=YOUR-API-KEY
```
* Add steps to [Download and install the Blaxel CLI](/cli-reference/introduction#install) in your CI environment and log in to your Blaxel workspace:
```shell theme={null}
bl login YOUR-WORKSPACE
```
You can now use the Blaxel CLI to interact with your workspace, including creating sandboxes, executing batch jobs, or deploying agents or MCP servers.
For security, you should always create a separate API key for your CI environment. [Create an API key in the Blaxel Console](https://app.blaxel.ai/profile/security).
### Use API keys via middleware
If you wish to call the Blaxel API directly from a client Web application, you would have to include your Blaxel API key in client-side code. This creates a serious security vulnerability, as the key will become visible in network requests or in the source code on the client end of the connection.
The solution is to add authentication middleware between your client application and the Blaxel API. The middleware serves as a secure "translator", which intercepts requests from the client application, injects your Blaxel API key, and then forwards them to Blaxel.
This solution requires a catch-all route in the client application (e.g. `/api/blaxel`) that forwards all SDK requests to Blaxel's API. The route reads the Blaxel workspace and API key securely from host environment variables (e.g. `BL_WORKSPACE` and `BL_API_KEY`) and injects them into each request by adding `Authorization` and `X-Blaxel-Workspace` headers.
Here is a sample implementation of this catch-all route implemented in Next.js:
The above code is illustrative and not intended for production use. It has no authentication, allowing access to anyone who knows the endpoint URL. You must add authentication (session validation, API keys, rate limiting) before deploying in production.
```typescript theme={null}
import { NextRequest, NextResponse } from 'next/server';
async function handleRequest(request: NextRequest, { params }: { params: { slug: string[] } }) {
params = await params;
const path = params.slug.slice(1).join('/');
const workspace = process.env.BL_WORKSPACE;
const apiKey = process.env.BL_API_KEY; // Securely stored in environment variables
const headers = new Headers(request.headers);
headers.set('Authorization', `Bearer ${apiKey}`);
headers.set('X-Blaxel-Workspace', workspace);
const fetchOptions: RequestInit = {
method: request.method,
headers,
body: ['GET', 'HEAD'].includes(request.method) ? undefined : await request.text(),
};
const response = await fetch(`https://api.blaxel.ai/v0/${path}`, fetchOptions);
const responseHeaders = new Headers(response.headers);
responseHeaders.delete('content-encoding');
responseHeaders.delete('content-length');
return new NextResponse(response.body, {
status: response.status,
headers: responseHeaders,
});
}
export { handleRequest as DELETE, handleRequest as GET, handleRequest as PATCH, handleRequest as POST, handleRequest as PUT };
```
Now, within the application, the Blaxel SDK should be configured at init-time to use the catch-all route, as shown below. With this change, SDK requests are routed through the middleware rather than directly transmitted to the Blaxel API.
```typescript theme={null}
import blaxel from "@blaxel/core";
blaxel.initialize({
proxy: 'http://localhost:3000/api/blaxel', // Your catch-all route
workspace: 'YOUR_WORKSPACE',
});
// Now use the SDK as normal
const sandbox = await blaxel.SandboxInstance.create({
// ...
});
```
Under this approach, your Blaxel API key is never exposed to the calling client, ensuring security. SDK requests continue to work as before, except that now they are routed through the catch-all route rather than directly to the Blaxel API.
### Manage API keys
You can create private API keys for your Blaxel account to authenticate directly when using the Blaxel APIs or CLI. Your [permissions in each workspace](/Security/Workspace-access-control) will be the ones given to your account in each of them.
API keys can be managed from the Blaxel console in **Profile > Security**.
For production-grade access to workspace resources that should be independent of individual users, it's strongly recommended to use [service accounts](/Security/Service-accounts) in the workspace.
## OAuth 2.0 tokens
These short-lived tokens are based on the [OAuth 2.0](https://oauth.net/2/) authentication protocol, and have a validity period of **2 hours**.
### Use OAuth tokens in the CLI
Use the `bl login` command.
You will then be redirected to the Blaxel console to finish logging in. Sign in using your Blaxel account if you aren’t already.
Once this is done, return to your terminal: the login will be finalized and you will then be able to run CLI commands.
Your [permissions in each workspace](/Security/Workspace-access-control) will be the ones given to your account in each of them.
### Use OAuth tokens in the API via service accounts
[Service accounts](/Security/Service-accounts) can retrieve a short-lived token via the *OAuth client credentials grant type* in the authentication API, using their *client ID* and *client secret*. These two keys are generated automatically when creating a service account. Make sure to copy the secret at its generation as you will never be able to see it again after.
Service accounts can also connect to the API using a long-lived API key, as detailed [in the section below](/Security/Access-tokens).
To retrieve the token, pass the service account’s *client ID* and *client secret* in the header to the `/oauth/token` endpoint.
```bash theme={null}
curl --request POST \
--url https://api.blaxel.ai/v0/oauth/token \
--header 'Authorization: Basic base64(CLIENT_ID:CLIENT_SECRET)' \
--header 'Content-Type: application/json' \
--data '{
"grant_type":"client_credentials"
}'
```
Alternatively, you can also pass the *client ID* and *client secret* in the body:
```bash theme={null}
curl --request POST \
--url https://api.blaxel.ai/v0/oauth/token \
--header 'Content-Type: application/json' \
--data '{
"grant_type":"client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
}'
```
You will retrieve a bearer token valid for 2 hours, which can then be passed in any call to the Blaxel APIs through any of the following headers: `Authorization` or `X-Blaxel-Authorization`, as such:
```bash theme={null}
curl 'https://api.blaxel.ai/v0/models' \
-H 'Accept: application/json, text/plain, */*' \
-H 'X-Blaxel-Authorization: Bearer YOUR_TOKEN'
```
**Common errors**: a missing or incorrect `grant_type` value, or an invalid/missing `client_secret`, both return a `400` error describing the specific parameter that's invalid or missing. Double check the *client ID* and *client secret* were copied correctly — the secret is only shown once at creation and can't be retrieved again.
### (Advanced) Use OAuth tokens in the APIs
This section assumes you are a developer experienced with OAuth 2.0. For a simpler guide of how to use short-lived tokens in Blaxel APIs, read the [section on authenticating service accounts](/Security/Access-tokens).
Blaxel implements all **grant types** in the OAuth 2.0 convention, including [client credentials](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/), [authorization code](https://www.oauth.com/oauth2-servers/access-tokens/authorization-code-request/), and [refresh tokens](https://www.oauth.com/oauth2-servers/access-tokens/refreshing-access-tokens/). If you are a developer experienced with OAuth 2.0, you can find the **well-known configuration endpoint** at the following URL:
```text theme={null}
https://api.blaxel.ai/v0/.well-known/openid-configuration
```
Through the endpoints discoverable in the aforementioned URL, you can implement any authentication flow in your application, and use the retrieved tokens in any of the following headers: `Authorization` or `X-Blaxel-Authorization`.
Alternatively, you can retrieve a token using the SDK:
```tsx theme={null}
import { settings } from "@blaxel/core"
await settings.authenticate() // Refreshes the token only if needed
console.log(settings.token)
```
*Note: when using the Blaxel SDK to operate Blaxel (e.g. to create an agent), token retrieval and refresh [are done automatically based on either being authenticated with CLI or using environment variables](../sdk-reference/introduction).*
# Baseten managed workspaces
Source: https://docs.blaxel.ai/Security/Baseten-managed-workspaces
Connect your Baseten organization to a managed Blaxel workspace and manage access and account settings.
Signing in with Baseten connects you to the Blaxel workspace for your Baseten organization. Baseten is the source of truth for organization membership, while Blaxel provides the workspace where your team runs resources.
## Organization and account mapping
Each Baseten organization maps to one dedicated Blaxel workspace and one dedicated Blaxel account.
The Blaxel account is created for the organization. It holds the managed workspace and its account-level settings, including usage, quotas, and billing. Blaxel creates a new account upon login. Your existing account, if you have one, is not used.
A Baseten managed account can only contain its managed workspace. You cannot create another workspace in that account or delete the managed workspace from Blaxel.
## Connect your organization
A Baseten organization admin must sign in once before anyone else in the organization can use Blaxel.
* Open the [Blaxel Console](https://app.blaxel.ai)
* Select Continue with Baseten
* Sign in as a Baseten organization admin
During this first sign-in, Blaxel identifies the admin's Baseten organization, provisions its managed account and workspace, and gives the admin Blaxel workspace admin access. Each later Baseten sign-in connects the user to the workspace mapped to their organization.
If provisioning does not finish, an organization admin can sign in again to retry it. A non-admin cannot create the workspace. They must wait for a Baseten organization admin to initialize the account first.
## Access and roles
Blaxel checks the user's Baseten organization membership every time they sign in with Baseten. It grants the corresponding Blaxel workspace role and updates a role that changed in Baseten.
| Baseten organization role | Blaxel workspace role | Access |
| - | - | - |
| Admin | Admin | Full workspace access, except member management, which stays in Baseten |
| Creator | Member | Standard member access to workspace resources |
If a user joins a different Baseten organization, their next Baseten sign-in connects them to the new organization's Blaxel workspace. Blaxel removes their access to any previous Baseten managed workspace during that sign-in.
## Add members
Add people to the Baseten organization, not to the Blaxel workspace. Follow the [Baseten access-control documentation](https://docs.baseten.co/organization/access) to invite or change organization members.
* Sign in to [Baseten](https://app.baseten.co) and add the user to your organization as an admin or creator
* Have the user open the Blaxel Console and select Continue with Baseten
Blaxel automatically gives the user access to the organization's managed workspace when they sign in. Do not invite the user from the Blaxel workspace team page, CLI, or API. Blaxel rejects membership changes there because they would not be reflected in Baseten.
## Remove members
Remove the user from the Baseten organization first. Follow the [Baseten access-control documentation](https://docs.baseten.co/organization/access) to manage organization members. This prevents them from signing in to the managed workspace with Baseten, and Blaxel removes stale managed-workspace access if the user later attempts a Baseten sign-in.
To complete offboarding immediately, manually delete the user's Blaxel workspace access on the Blaxel side. This deletion is not an immediate reverse sync from Baseten. Managed workspace membership cannot be edited from the Blaxel Console, CLI, or API, so contact [Blaxel support](mailto:support@blaxel.ai) to remove the access manually. For standard Blaxel workspaces, see [Remove a member](/Security/Workspace-access-control#remove-a-member).
Removing a user from Baseten does not delete their Blaxel user identity. Remove their Baseten membership and request the manual Blaxel access deletion when you need access revoked immediately.
## Manage the Blaxel account
The first Baseten admin to initialize the organization becomes the owner of its Blaxel account. That ownership is separate from the Baseten workspace role. Later Baseten admins receive Blaxel workspace admin access when they sign in, but they do not automatically become Blaxel account admins or replace the account owner.
The Blaxel account owner can manage account-level settings and add Blaxel account admins when others need to manage those settings. Changing a user's role or removing them in Baseten changes their workspace access on their next Baseten sign-in. It does not transfer Blaxel account ownership.
Learn about access control for standard Blaxel workspaces.
Review account-level quotas, credits, and usage analytics.
# Billing Explorer API
Source: https://docs.blaxel.ai/Security/Billing-Explorer-API
Programmatically retrieve cost and usage metrics for your Blaxel account through the Billing Explorer API, with auth and request examples.
The **Billing API** is the same data source that powers the [Billing Explorer](/Security/Quotas#analytics) in the Blaxel Console. It returns resource-centric, paginated timeseries of cost and usage for an account over an arbitrary time window, with optional filtering and grouping.
Use it to:
* pull cost and usage into your own observability stack
* build custom dashboards and chargeback reports per workspace, resource type, or individual resource
* drive alerting on spend trends
The Billing API is an **account-scoped** API. Unless you pass `workspace=` or `groupBy=workspace` (both restricted to account admins — see [Admin-only parameters](/api-reference/billing#admin-only-parameters)), the response aggregates across every workspace in the account.
**Metrics are not real-time.** We aggressively minimize the delay, but usage events flow through a metering pipeline before they become queryable — expect a lag of a few minutes (sometimes longer under load) between a resource running and its cost/usage appearing in the response. Do not use this API for hard real-time enforcement; use it for dashboards, reporting, and alerting.
Read the reference documentation.
# Data collection and privacy
Source: https://docs.blaxel.ai/Security/Data-collection-and-privacy
Understand what usage and telemetry data the Blaxel CLI and SDKs collect, how it is used, and how to opt out for privacy-sensitive deployments.
The Blaxel SDKs and Blaxel CLI have two separate data collection systems.
## Error tracking
When enabled, the SDKs and CLI capture errors and exceptions that originate from the SDK/CLI itself - not from application code. Each SDK/CLI has a lightweight, custom Sentry client that collects and tracks the following error-related metadata:
* Error type and message
* Stack trace
* Environment
* Release/version
* Workspace
* Commit hash
* OS/runtime context
* Breadcrumbs
* Runtime-specific context (e.g. goroutine info in Go)
No request data, API keys, file contents, IP addresses or other user or application data is collected.
### Opt in
Error tracking is controlled by a combination of the `DO_NOT_TRACK` environment variable and the `tracking` parameter, read from the following locations (in priority order):
1. `DO_NOT_TRACK` environment variable
2. `tracking:` field in `~/.blaxel/config.yaml`
3. Pre-set default value
* `false` starting from SDK v0.2.46 (Python), v0.2.76 (TypeScript), and v0.16.1 (Go)
* `true` in all earlier versions
The Blaxel CLI prompts the user for tracking consent on first interactive use and during installation. The response is recorded in `~/.blaxel/config.yaml` and affects error tracking in the SDKs and the CLI locally.
Consenting when prompted sets `tracking: true` in `~/.blaxel/config.yaml`, which is read by both the CLI and SDKs and results in error tracking becoming enabled for local sessions.
However, when deploying on Blaxel, the platform automatically injects `DO_NOT_TRACK=1`, which disables error tracking regardless of the value set in `~/.blaxel/config.yaml`. You can override this by explicitly setting `DO_NOT_TRACK=0` in your environment configuration (see below). Similarly, in CI environments or in remote environments where the Blaxel CLI is not used, the `tracking:` field is never set to `true` and error tracking is therefore disabled by default.
#### `DO_NOT_TRACK` environment variable
The `DO_NOT_TRACK` environment variable follows the [Console Do Not Track](https://consoledonottrack.com/) convention. To explicitly enable tracking, use the command below in your shell environment:
```bash theme={null}
export DO_NOT_TRACK=0 # Enable tracking
```
#### Blaxel configuration file
You can also disable tracking by adding the following parameter to your `~/.blaxel/config.yaml` global configuration file:
```yaml theme={null}
tracking: false
```
Setting `BL_ENABLE_OPENTELEMETRY=0` has no effect on error tracking.
## OpenTelemetry tracing and logging
When you deploy from the Blaxel Console, Blaxel CLI or by using the Blaxel SDK to wrap your code for deployment, Blaxel automatically instruments your requests with logging and tracing. The resulting logs and traces are provided to users for real-time monitoring and debugging of their workloads.
The data collected consists of:
* Metrics: Aggregated data about workload executions
* Logs: Timestamped logs for workloads
* Traces: Data on inputs, outputs and execution steps
Blaxel only collects and saves traces for a sampled 10% of all your executions.
[Read more about observability](/Observability/Overview).
### Opt in locally
This feature is controlled by the `BL_ENABLE_OPENTELEMETRY` environment variable. This environment variable defaults to `false` locally and to `true` in deployed environments. To explicitly enable it locally, use the command below in your shell environment:
```bash theme={null}
export BL_ENABLE_OPENTELEMETRY=true # Enable OpenTelemetry logging
```
Setting `DO_NOT_TRACK=1` has no effect on OpenTelemetry.
## Sandbox runtime error reporting
The Blaxel sandbox runtime (`sandbox-api`) reports internal errors to Sentry. The data collected is limited to:
* Error type and message
* Stack trace
* Environment
* Release/version
* Runtime context (e.g. goroutine info)
Request context and user data are unconditionally stripped server-side before any data leaves the process. No request payloads, API keys, user identifiers, or IP addresses are transmitted.
When telemetry is active, `sandbox-api` logs an explicit notice at startup.
### Opt out
Sandbox runtime telemetry is controlled independently from the SDK/CLI `DO_NOT_TRACK` variable. Use either of the following in your sandbox image configuration:
```bash theme={null}
# Environment variable
TELEMETRY_ENABLED=false
# Startup flag in entrypoint script
/usr/local/bin/sandbox-api --disable-telemetry &
```
`TELEMETRY_ENABLED` and `--disable-telemetry` only affect sandbox runtime error reporting. They have no effect on SDK/CLI error tracking (controlled by `DO_NOT_TRACK`) or OpenTelemetry tracing (controlled by `BL_ENABLE_OPENTELEMETRY`).
# Domain capture
Source: https://docs.blaxel.ai/Security/Domain-capture
Verify your company's email domain on Blaxel to control which login methods your team can use and automatically onboard new users to workspaces.
## Overview
Domain capture lets you claim ownership of your organization's email domain (e.g. `acme.com`) and then decide exactly how users from that domain can sign in. You can restrict logins to specific methods (Google, SSO (SAML), or email passwordless) and automatically add new users to your workspaces when they first sign in.
This feature is available to all account administrators at no extra cost.
SAML SSO and Directory Sync are enterprise features built on top of domain verification. See [SSO & Directory Sync](/Security/SSO-Directory-sync) if you need those.
## Prerequisites
* You must be an account administrator.
* You must have access to your domain's DNS settings to add a TXT record.
## Step 1: Add a domain
Go to **Account Settings** → **Identity & Access**.
Type your company's email domain (e.g. `acme.com`) in the input field and click **Add domain**.
The domain appears in the list with a **Pending** status. It will remain inactive until you complete DNS verification.
## Step 2: Verify via DNS TXT record
To prove you own the domain, add a DNS TXT record provided by Blaxel.
Click **Show DNS** on the pending domain row.
You'll see two values:
* **Name**: the hostname to add the record to (e.g. `_blaxel-sso-verification.yourdomain.com`)
* **Value**: the verification string starting with `blaxel-sso-verify=...`
Click the copy icon next to each value.
Log in to your DNS provider (Cloudflare, Route 53, GoDaddy, etc.) and add a new TXT record with the name and value you copied.
DNS propagation typically takes a few minutes, but can take up to 48 hours in rare cases.
Return to **Account Settings** → **Identity & Access** and click **Verify** on the domain row.
Once verified, the domain status changes to **Verified** (green checkmark) and additional options appear below it.
If verification fails, double-check that the TXT record name and value are entered exactly as shown. Some DNS providers automatically append the root domain. Confirm the full record name in your DNS provider's interface.
## Step 3: Set allowed auth methods
After your domain is verified, you can restrict which login methods users from that domain can use.
Click a method badge to toggle it on or off:
| Method | Description |
| - | - |
| **Google** | Sign in with a Google account |
| **SSO (SAML)** | Sign in through your SAML identity provider (requires SSO setup) |
| **Email (passwordless)** | Sign in via email magic link or OTP |
If no methods are selected, there is no restriction and users can sign in with any available method.
If SAML SSO is configured and active on your account, **SSO (SAML)** becomes the only allowed method automatically and the other toggles are locked. See [SSO & Directory Sync](/Security/SSO-Directory-sync).
## Step 4: Configure auto-join workspaces
You can automatically add new users to one or more workspaces the first time they sign in with your verified domain.
Under **Auto-join**, toggle on any workspace to enable automatic membership for users from that domain.
Users who are already logged in when domain capture is toggled on will not be automatically added to the workspace until they log out and log back in.
If Directory Sync is also active, workspace membership may be managed by two systems simultaneously. Prefer using Directory Sync group mappings to control workspace membership when Directory Sync is configured.
## Removing a domain
Click the trash icon on any domain row to remove it. You'll be asked to confirm before deletion.
A domain that is actively linked to a SAML SSO connection cannot be deleted until the SSO connection is removed first.
## Multi-factor authentication
Multi-factor authentication (MFA) is available for all login methods except SAML/SSO. Users can enroll a TOTP authenticator app or an SMS factor from their account settings and will be prompted for the second factor at each login. MFA is not available when SAML SSO is active, since identity verification is handled by your identity provider.
## Related
* [SSO & Directory Sync](/Security/SSO-Directory-sync)
* [Workspace Access Control](/Security/Workspace-access-control)
# Usage and quotas
Source: https://docs.blaxel.ai/Security/Quotas
Monitor account usage in the Blaxel Console, view current quotas for sandboxes, agents, jobs, and other resources, and request quota increases.
The Blaxel Console provides detailed analytics for you to monitor account usage. You can also see your account's current quotas, and request an increase in quotas if required.
## Quotas
### Quota tiers
Blaxel has a quota tiering system that unlocks higher limits and features on the platform as your tier progresses.
Higher tiers have access to higher limits such as more standby/concurrent sandboxes, more concurrent jobs, higher sandbox and volume sizes, longer sandbox TTLs. They also have access to gated features such as custom domains, dedicated IPs and more.
You can see the [limits and features of each tier](https://app.blaxel.ai/account/quotas) in the **Quotas** tab.
* Click the workspace name at the bottom left of the Blaxel Console.
* In the sub-menu, click **Account**.
* Click the **Quotas** tab.
#### How tiers are calculated
Your current tier is evaluated continuously based on your **30-day top-up volume**. This is the total amount of real funds added to your Blaxel wallet via manual or automated top-ups over the trailing 30 days.
**Top-ups are not flat fees**: Funds added to meet a tier requirement are deposited directly into your account balance. These funds are subsequently used to pay for your standard compute and storage consumption.
**Promotional credits do not unlock tiers**: Promotional credits (such as startup program grants) and previously accumulated balances automatically apply toward your active infrastructure costs. However, they do not count toward the 30-day top-up volume required to unlock or maintain a quota tier.
#### Maintaining your tier
Because tier eligibility relies on a rolling 30-day window, your tier will automatically adjust if your top-up volume decreases. If your 30-day top-up volume falls below your current tier's threshold, your workspace will be downgraded, which may result in blocked resource creation if you exceed the lower tier's limits.
To prevent service disruptions and maintain your quotas without manual intervention, you can configure **Auto top-up** in the Billing section of your Console.
### Quota usage
Blaxel also lets you view and manage quotas associated with your account.
Quotas for your account are listed in the **All current quotas** section.
This section provides details on quotas for:
* Deployed sandboxes, agents, MCP servers, model APIs, policies, triggers, and jobs
* Sandboxes: volumes, volume templates, default TTL, previews, volume sizes and versions
* Jobs: maximum parallel executions, maximum concurrent tasks
* Workspaces: users, service accounts, integrations, custom domains, revisions, timeouts
### Increase account quotas
#### Automatically increase quotas via top-up
To unlock access to [higher quotas and limits](https://app.blaxel.ai/account/quotas), you must upgrade to a higher tier by topping up the appropriate amount of credit in your account. These top-up amounts go directly into your Blaxel wallet. This is not a fee for the tier itself, but rather a pre-payment for the usage you are about to run.
### Manually request a quota increase
**Upgrading to a higher tier** is the usual way to unlock higher quotas for your account. In case of issues, it is also possible to manually request an increase in quotas for your account through the Blaxel Console. To do this:
* Click the pencil icon.
* Input the requested quota value.
In some cases, you may see an **Upgrade** button instead of a pencil icon. Click this to access our payment gateway, in which you can provide details for payment.
Accounts on the free plan are not eligible for quota upgrades and will not see the option to request a quota increase in the Blaxel Console.
## Best practices for quota management
### Configure sandbox TTL
Keeping inactive sandboxes in standby consumes snapshot storage and counts against your quota limits. To optimize costs and quota usage, we recommend setting a Time-To-Live (TTL) of 7 to 60 days (depending on your re-activation patterns) to automatically prune dormant sandboxes.
### Monitor usage alerts
Blaxel automatically dispatches an email notification to workspace administrators when your infrastructure usage reaches 90% of your current tier's quota.
## Analytics
Blaxel Billing Explorer provides detailed cost and usage analytics at account level. These metrics are available for the current billing period, last 30 days, last 7 days, and last 24 hours.
To view usage analytics for your account:
* Click the workspace name at the bottom left of the Blaxel Console.
* Click **Account**.
* Click the **Subscription** tab.
* Navigate to the **Usage analytics** section of the page.
The usage analytics section provides details on:
* Total compute runtime usage/cost for sandboxes, agents, batch jobs or MCP servers
* Total deployment image and snapshot storage usage/cost
* Total volume storage usage/cost
* Total number/cost of asynchronous requests
You can toggle between usage and cost metrics using the switch in the top right corner of the section.
# SAML & Directory Sync
Source: https://docs.blaxel.ai/Security/SSO-Directory-sync
Configure SAML single sign-on and automated user provisioning via Directory Sync (SCIM) to manage your organization's Blaxel access at scale.
## Overview
Blaxel supports enterprise-grade identity management through two features:
* **SAML SSO**: Let your employees sign in through your existing identity provider (Okta, Azure AD, Google Workspace, OneLogin, etc.)
* **Directory Sync (SCIM)**: Automatically provision and deprovision workspace memberships based on your identity provider's directory groups
Both features require the **SAML** feature flag on your account. [Contact us](https://blaxel.ai/contact) or email [support@blaxel.ai](mailto:support@blaxel.ai) to get access.
## Prerequisites
* Account administrator role
* The `saml` feature flag enabled on your account
* At least one **verified domain**: complete [Domain Capture](/Security/Domain-capture) first
***
## SAML SSO
### How it works
Once configured, users from your verified domain are redirected to your identity provider's login page instead of seeing the default Blaxel login options. After authenticating with your IdP, they are signed in to Blaxel automatically.
When SAML is active, it becomes the **only** allowed authentication method for your domain. Other methods (Google, email, etc.) are locked out.
### Set up SAML SSO
Go to **Account Settings** → **Identity & Access**.
If you haven't already, add and verify your company domain. See [Domain Capture](/Security/Domain-capture).
Scroll to **SAML Identity Provider** and click **Configure SAML Provider**.
This opens the SSO Admin Portal in a new tab.
In the SSO portal, follow the step-by-step instructions for your IdP. We provide setup guides for all major providers including Okta, Azure AD, Google Workspace, and OneLogin.
Return to **Account Settings** → **Identity & Access**. The **SAML Identity Provider** section shows **Active** with the provider name and connection name once setup is complete.
### Single Logout (SLO)
When a SAML user signs out of Blaxel, they are also signed out of your identity provider if your IdP supports Single Logout. No additional configuration is required on the Blaxel side.
***
## Directory Sync (SCIM)
### How it works
Directory Sync connects your identity provider's directory to Blaxel. When you add or remove users from groups in your IdP, Blaxel automatically adds or removes them from the corresponding workspaces.
### Set up Directory Sync
Go to **Account Settings** → **Identity & Access**.
Scroll to **Directory Sync (SCIM)** and click **Configure Directory Sync**.
This opens the Admin Portal in a new tab.
In the portal, select your directory provider and follow the setup instructions.
After connecting, configure group-to-workspace mappings so that members of each group are automatically provisioned into the right workspaces with the right roles.
Once active, the **Directory Sync (SCIM)** section shows **Active** with the provider type and directory name.
### Viewing membership source in the team table
The **Workspace Settings → Team** table includes a **Source** column that shows how each member joined the workspace.
| Source | Meaning |
| - | - |
| **Directory Sync** | Provisioned automatically by Directory Sync |
| **Invitation** | Joined via an email invitation |
| **Domain Capture** | Auto-joined because their email domain matched a domain policy |
| **Local** | Added directly within Blaxel |
### Deprovisioning
When a user is removed from a synced group in your IdP, Blaxel automatically removes their workspace membership on the next sync event. Their Blaxel account is not deleted; only the workspace membership is removed.
Avoid manually removing members who were provisioned by Directory Sync, as they will be re-added on the next sync. Manage membership through your identity provider instead.
***
## Related
* [Domain Capture](/Security/Domain-capture)
* [Workspace Access Control](/Security/Workspace-access-control)
# Service accounts
Source: https://docs.blaxel.ai/Security/Service-accounts
Use Blaxel service accounts with non-user API credentials to automate the full life-cycle of resources in CI/CD pipelines and backend systems.
Service accounts are workspace users (i.e. identities) that represent an external system that needs to access Blaxel to operate resources in **one specific workspace**.
## Authentication of service accounts
### API keys
Service accounts can use [API keys](/Security/Access-tokens) to authenticate on Blaxel. These API keys can be created and managed by admins from the service account’s page, in your workspace settings.
API keys can have an infinite validity duration.
### Client credentials (OAuth 2.0)
Service accounts can also use the *client credentials* [OAuth 2.0 grant type](/Security/Access-tokens), via their **client ID** and **client secret**. A pair of client ID / client secret is generated automatically by Blaxel when you create a new service account.
Make sure to copy the client secret when you create the service account as you will never be able to access it again after you leave the page.
## Permissions of service accounts
Service accounts can have [similar permissions](/Security/Workspace-access-control) as other users from your team. These permissions are managed by admins in your workspace settings.
# Workspaces, users and roles
Source: https://docs.blaxel.ai/Security/Workspace-access-control
Manage Blaxel workspaces, invite users, assign roles, and configure access control to share resources securely across your organization.
All resources on Blaxel are logically regrouped in an **account**, which is the highest possible level of tenancy. An account can have **multiple workspaces** — a common pattern when dealing with multiple environments (e.g. development vs. production), business units, or end-clients.
Users can be either [team members](/Security/Workspace-access-control), or [service accounts](/Security/Service-accounts) that represent external systems that can operate Blaxel. They are added to a workspace with certain permissions on the workspace resources inherited from their role.
## Workspace name
The workspace name (called `name` in the Blaxel API) uniquely identifies your workspace. You set it when the workspace is created. Once set, it cannot be changed. The workspace name appears at the bottom of the left sidebar in the Blaxel Console.
Each workspace also has a display name for better organization, which workspace admins can modify.
## User roles
There are two roles that a user or service account can have in a workspace: **admin** and **member**.
Admins have **complete access** in the workspace, on all workspace resources. They can also modify all workspace settings, including inviting other team members. More specifically, admins have all the permissions that members have, in addition to:
* creating and editing policies
* inviting and removing users
* changing users' permissions
* adding and removing integrations
* changing the workspace name
* deleting the workspace
Members can view the workspace settings but not edit them. They are also able to **view and modify** the following resources inside a workspace (including querying them when applicable):
* [agents](../Agents/Overview)
* [MCP servers](../Functions/Overview)
* [model APIs](../Models/Model-deployment)
* [batch jobs](../Jobs/Overview)
* [sandboxes](../Sandboxes/Overview)
They have read-only access on the following resources:
* [policies](../Model-Governance/Policies)
## Shared account settings
A Blaxel account is the top-level entity that owns one or more workspaces. The following are managed at the account level and are **shared across all workspaces** in an account:
* Quota tier and limits
* Credits
* SSO/SAML configuration
* Usage analytics
This means that resource usage in any workspace draws from the common credit balance and counts toward the same quotas. See the [usage and quotas documentation](./Quotas) for more information.
## Create a workspace
Workspaces can be created, managed, and deleted using the Blaxel Console and the Blaxel SDKs.
### Blaxel Console
To create a new workspace using the Blaxel Console:
* Click the workspace name at the bottom left corner.
* In the rollout menu, click **Switch** > **+**.
* In the **Create a new workspace** dialog, enter a workspace name and label.
* Click **Create** to create the workspace.
### Blaxel SDKs
```typescript TypeScript theme={null}
import { createWorkspace } from "@blaxel/core";
const { data: newWs } = await createWorkspace({
body: {
name: "my-workspace",
displayName: "My Workspace",
region: "us-west-2",
},
});
console.log(newWs);
```
```python Python theme={null}
import asyncio
from blaxel.core.common import autoload
from blaxel.core.client import client
from blaxel.core.client.api.workspaces import create_workspace
from blaxel.core.client.models.workspace import Workspace
autoload()
async def main():
new_ws = await create_workspace.asyncio(
client=client,
body=Workspace(
name="my-workspace",
display_name="My Workspace",
region="us-west-2",
),
)
print(new_ws)
asyncio.run(main())
```
## Delete a workspace
Deleting a workspace is permanent and cannot be undone.
### Blaxel Console
To delete a workspace using the Blaxel Console:
* Click the workspace name at the bottom left corner.
* Click **Workspace settings**.
* Review the information and click **Delete workspace** to delete the workspace.
### Blaxel SDKs
```typescript TypeScript theme={null}
import { deleteWorkspace } from "@blaxel/core";
const { data: deleted } = await deleteWorkspace({
path: {
workspaceName: "my-workspace",
},
});
console.log(deleted);
```
```python Python theme={null}
import asyncio
from blaxel.core.common import autoload
from blaxel.core.client import client
from blaxel.core.client.api.workspaces import delete_workspace
autoload()
async def main():
deleted = await delete_workspace.asyncio(
workspace_name="my-workspace",
client=client,
)
print(deleted)
asyncio.run(main())
```
## Invite a member
Admins can invite team members via their email address. They will be prompted for the role to give the user.
To invite a team member:
* Click **Workspace** > **Team**.
* Click **Invite user**.
* Enter the invitee's email address and role.
The invitee will receive an email to allow them to accept the invitation on the Blaxel console. They will not be able to access workspace resources until they have manually accepted the invitation. If the user doesn't have a Blaxel account already, they will be asked to sign up first.
Invitations to other workspaces are visible from your profile.
## Remove a member
To remove a member from a standard Blaxel workspace:
* Click **Workspace** > **Team**
* Select **Remove** from the member's actions menu
* Confirm the removal
Only workspace admins can remove members. You cannot remove yourself; another workspace admin must remove you.
# Run Google ADK on Blaxel
Source: https://docs.blaxel.ai/Tutorials/ADK
Deploy Google Agent Development Kit (ADK) projects to Blaxel with minimal code changes to get serverless hosting, observability, and policies.
You can deploy your [Google Agent Development Kit (ADK)](https://github.com/google/adk-python/tree/main) projects to Blaxel with minimal code editing (and zero configuration), enabling you to use [Serverless Deployments](../Infrastructure/Global-Inference-Network), [Agentic Observability](../Observability/Overview), [Policies](../Model-Governance/Policies), and more.
## Get started with ADK on Blaxel
To get started with ADK on Blaxel:
* if you already have an ADK agent, adapt your code with [Blaxel SDK commands](../Agents/Develop-an-agent) to connect to [MCP servers](../Functions/Overview), [LLMs](../Models/Overview) and [other agents](../Agents/Overview).
* else initialize an example project with ADK by using the following Blaxel CLI command and selecting the *Google ADK hello world:*
```bash theme={null}
bl new agent
```
[Deploy](../Agents/Deploy-an-agent) it by running:
```bash theme={null}
bl deploy
```
## Develop an ADK agent using Blaxel features
While building your agent in ADK, use Blaxel [SDK](../sdk-reference/introduction) to connect to resources already hosted on Blaxel:
* [MCP servers](../Functions/Overview)
* [LLMs](../Models/Overview)
* [other agents](../Agents/Overview)
### Connect to MCP servers
Connect to [MCP servers](../Functions/Overview) using the Blaxel SDK to access pre-built or custom tool servers hosted on Blaxel. This eliminates the need to manage server connections yourself, with credentials stored securely on the platform.
Run the following command to retrieve tools in ADK format:
```python Python theme={null}
from blaxel.googleadk import bl_tools
await bl_tools(['mcp-server-name'])
```
### Connect to LLMs
Connect to [LLMs](../Models/Overview) hosted on Blaxel using the SDK to avoid managing model API connections yourself. All credentials remain securely stored on the platform.
```python Python theme={null}
from blaxel.googleadk import bl_model
model = await bl_model("model-api-name")
```
### Connect to other agents
Connect to other agents hosted on Blaxel from your code by using the [Blaxel SDK](../sdk-reference/introduction). This allows for multi-agent chaining without managing connections yourself. This command is independent of the framework used to build the agent.
```python Python theme={null}
from blaxel.core.agents import bl_agent
response = await bl_agent("agent-name").run(input);
```
### Host your agent on Blaxel
You can [deploy](../Agents/Deploy-an-agent) your agent on Blaxel, enabling you to use [Serverless Deployments](../Infrastructure/Global-Inference-Network), [Agentic Observability](../Observability/Overview), [Policies](../Model-Governance/Policies), and more. This command is independent of the framework used to build the agent.
Either run the following CLI command from the root of your agent repository.
```bash theme={null}
bl deploy
```
Or [connect a GitHub repository to Blaxel](../Agents/Github-integration) for automatic deployments every time you push on *main*.
# Agent framework tutorials overview
Source: https://docs.blaxel.ai/Tutorials/Agents-Overview
Browse framework-specific tutorials for shipping AI agents on Blaxel using LangChain, CrewAI, LlamaIndex, ADK, AI SDK, PydanticAI, and more.
Blaxel is a fully framework-agnostic infrastructure platform that helps you build and host your agents. It supports a **range of the most popular AI agent frameworks and harnesses**, optimizing how your agent builds and runs no matter how you coded it.
Blaxel's platform-agnostic design lets you deploy your code either on Blaxel or through traditional methods like Docker containers. When deploying on Blaxel, your agent goes through a specialized build process that gives it access to Blaxel features through SDK commands in its code. This low-level SDK connects you to [MCP servers](../Functions/Overview), [LLM APIs](../Models/Overview) and [other agents](../Agents/Overview) that are hosted on Blaxel.
As such, you can build your agentic applications with anything from [LangChain](/Tutorials/LangChain) or [AI SDK](/Tutorials/Vercel-AI) to pure TypeScript or Python, or starting from a popular harness like [Claude](/Tutorials/Claude-Agent-SDK-Index) or [OpenClaw](/Tutorials/OpenClaw) -- and deploy them with minimal upfront setup.
Run policy-confined agents in isolated Blaxel microVM sandboxes.
Move DeepSeek Harness sessions into Blaxel sandboxes and return reviewed changes to your local repository.
Run coding agents in persistent Blaxel sandboxes and apply reviewed changes to your local worktree.
Run OpenClaw in a Blaxel sandbox.
Build and deploy Claude Agent SDK agents on Blaxel.
Deploy a self-hosted Claude Managed Agents environment on Blaxel.
Run multi-agent systems built with CrewAI on Blaxel.
Run LLM agents or workflows of agents on Blaxel using Google’s Agent Development Kit (ADK).
Build and deploy LangChain agents on Blaxel.
Deploy LlamaIndex agentic systems on Blaxel.
Use Mastra framework to develop agentic AI on Blaxel.
Orchestrate Blaxel agents using n8n workflows.
Leverage OpenAI Agents SDK to create Blaxel agents.
Create and deploy PydanticAI agents on Blaxel.
Host agents built with AI SDK on Blaxel.
Create and deploy custom agents in Python or TypeScript.
Learn how to [get started with Blaxel](../Get-started).
# Run Amp in a sandbox
Source: https://docs.blaxel.ai/Tutorials/Amp
Run Amp inside a Blaxel sandbox, stream coding tasks, and continue a thread after reconnecting.
This tutorial shows you how to run [Amp](https://ampcode.com) in a Blaxel sandbox. You will create a sandbox, install the Amp CLI, clone a repository, stream a coding task, and continue the same thread later.
## Prerequisites
You need:
* a Blaxel account;
* the [Blaxel CLI](/cli-reference/introduction#install);
* an [Amp access token](https://ampcode.com/settings/security);
* Node.js 24 or Python 3.10 or newer;
* a Git repository that the sandbox can clone.
Log in to Blaxel:
```shell theme={null}
bl login
```
Export your Amp token and repository URL:
```shell theme={null}
export AMP_API_KEY=YOUR-AMP-ACCESS-TOKEN
export REPOSITORY_URL=https://github.com/YOUR-ORGANIZATION/YOUR-REPOSITORY.git
```
The sandbox receives `AMP_API_KEY` as an environment variable. The example does not print it.
Headless Amp tasks can run commands without asking for confirmation. Use trusted repositories, private threads, a short sandbox TTL, and restricted network access for sensitive code.
## 1. Install the Blaxel SDK
Create a local project and install one Blaxel SDK:
```shell TypeScript (npm) theme={null}
npm init -y
npm pkg set type=module
npm install @blaxel/core
npm install --save-dev tsx
```
```shell Python theme={null}
python3 -m venv .venv
source .venv/bin/activate
pip install blaxel
```
## 2. Create the Amp sandbox
Create `index.ts` or `main.py` with the following code:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const repositoryUrl = process.env.REPOSITORY_URL;
const ampApiKey = process.env.AMP_API_KEY;
const repositoryDir = "/blaxel/work/repository";
if (!repositoryUrl) throw new Error("REPOSITORY_URL is required");
if (!ampApiKey) throw new Error("AMP_API_KEY is required");
function shellQuote(value: string): string {
return `'${value.replaceAll("'", `'"'"'`)}'`;
}
const sandbox = await SandboxInstance.createIfNotExists({
name: "amp-sandbox",
image: "blaxel/node-slim:latest",
memory: 8192,
region: "us-pdx-1",
ttl: "2h",
envs: [{ name: "AMP_API_KEY", value: ampApiKey }],
});
const setupResult = await sandbox.process.exec({
command: [
"command -v amp >/dev/null || npm install -g @ampcode/cli --silent",
`mkdir -p ${shellQuote(repositoryDir)}`,
`test -d ${shellQuote(`${repositoryDir}/.git`)} || git clone --depth 1 ${shellQuote(repositoryUrl)} ${shellQuote(repositoryDir)}`,
].join(" && "),
waitForCompletion: true,
timeout: 300,
});
if (setupResult.exitCode !== 0) {
throw new Error(`Sandbox setup failed with exit code ${setupResult.exitCode}`);
}
console.log(`Amp sandbox ready: ${sandbox.metadata.name}`);
```
```python Python theme={null}
import asyncio
import json
import os
import shlex
from blaxel.core import SandboxInstance
REPOSITORY_DIR = "/blaxel/work/repository"
async def main() -> None:
repository_url = os.environ.get("REPOSITORY_URL")
amp_api_key = os.environ.get("AMP_API_KEY")
if not repository_url:
raise RuntimeError("REPOSITORY_URL is required")
if not amp_api_key:
raise RuntimeError("AMP_API_KEY is required")
sandbox = await SandboxInstance.create_if_not_exists(
{
"name": "amp-sandbox",
"image": "blaxel/node-slim:latest",
"memory": 8192,
"region": "us-pdx-1",
"ttl": "2h",
"envs": [{"name": "AMP_API_KEY", "value": amp_api_key}],
}
)
setup_result = await sandbox.process.exec(
{
"command": " && ".join(
[
"command -v amp >/dev/null || npm install -g @ampcode/cli --silent",
f"mkdir -p {shlex.quote(REPOSITORY_DIR)}",
f"test -d {shlex.quote(f'{REPOSITORY_DIR}/.git')} || git clone --depth 1 {shlex.quote(repository_url)} {shlex.quote(REPOSITORY_DIR)}",
]
),
"wait_for_completion": True,
"timeout": 300,
}
)
if setup_result.exit_code != 0:
raise RuntimeError(f"Sandbox setup failed with exit code {setup_result.exit_code}")
print(f"Amp sandbox ready: {sandbox.metadata.name}")
if __name__ == "__main__":
asyncio.run(main())
```
Run the script:
```shell TypeScript (npm) theme={null}
npx tsx index.ts
```
```shell Python theme={null}
python main.py
```
The `blaxel/node-slim:latest` image ships Node.js 24, npm, Git, and the Blaxel sandbox API on Debian. The setup command installs the Amp CLI from npm on the first run and skips the install when `amp` is already present. The sandbox keeps its memory and files when it enters standby.
`createIfNotExists` reuses a sandbox with the same name. Delete and recreate the sandbox after you rotate `AMP_API_KEY`.
## 3. Run an Amp task
Add the following code after sandbox creation and repository cloning. In Python, add it inside `main()`.
```typescript TypeScript theme={null}
const prompt = "Inspect this repository and explain how to run its tests. Do not change any files.";
let ampOutput = "";
const result = await sandbox.process.exec({
name: "amp-task",
command: [
"amp",
"--mode low",
"--visibility private",
"--no-ide",
"--no-notifications",
"--no-color",
"--no-remote-control-terminal",
"--stream-json",
"--no-archive-after-execute",
`--execute ${shellQuote(prompt)}`,
].join(" "),
workingDir: repositoryDir,
waitForCompletion: true,
timeout: 600,
onStdout: (chunk) => {
ampOutput += chunk;
process.stdout.write(chunk);
},
onStderr: (chunk) => process.stderr.write(chunk),
});
if (result.exitCode !== 0) {
throw new Error(`Amp failed with exit code ${result.exitCode}`);
}
const ampEvents = ampOutput
.split("\n")
.map((line) => {
try {
return JSON.parse(line);
} catch {
return undefined;
}
});
const initEvent = ampEvents.find((event) => event?.type === "system" && event?.subtype === "init");
const resultEvent = ampEvents.find((event) => event?.type === "result");
const createdThreadId = initEvent?.session_id;
if (typeof createdThreadId !== "string") throw new Error("Amp did not return a thread ID");
if (resultEvent?.subtype !== "success" || resultEvent?.is_error !== false) {
throw new Error(`Amp thread ${createdThreadId} did not finish successfully`);
}
console.log(`Thread ID: ${createdThreadId}`);
```
```python Python theme={null}
prompt = "Inspect this repository and explain how to run its tests. Do not change any files."
amp_output: list[str] = []
def handle_stdout(chunk: str) -> None:
amp_output.append(chunk)
print(chunk, end="")
result = await sandbox.process.exec(
{
"name": "amp-task",
"command": " ".join(
[
"amp",
"--mode low",
"--visibility private",
"--no-ide",
"--no-notifications",
"--no-color",
"--no-remote-control-terminal",
"--stream-json",
"--no-archive-after-execute",
f"--execute {shlex.quote(prompt)}",
]
),
"working_dir": REPOSITORY_DIR,
"wait_for_completion": True,
"timeout": 600,
"on_stdout": handle_stdout,
"on_stderr": print,
}
)
if result.exit_code != 0:
raise RuntimeError(f"Amp failed with exit code {result.exit_code}")
amp_events = []
for line in "".join(amp_output).splitlines():
try:
amp_events.append(json.loads(line))
except json.JSONDecodeError:
continue
init_event = next(
(event for event in amp_events if event.get("type") == "system" and event.get("subtype") == "init"),
None,
)
result_event = next((event for event in amp_events if event.get("type") == "result"), None)
created_thread_id = init_event.get("session_id") if init_event else None
if not isinstance(created_thread_id, str):
raise RuntimeError("Amp did not return a thread ID")
if not result_event or result_event.get("subtype") != "success" or result_event.get("is_error") is not False:
raise RuntimeError(f"Amp thread {created_thread_id} did not finish successfully")
print(f"Thread ID: {created_thread_id}")
```
Amp returns structured JSON lines while it works. Store the printed thread ID if you need to continue the task later.
## 4. Continue the thread after reconnecting
Your application can reconnect after the sandbox enters standby. Create `continue.ts` or `continue.py` with the following code. Replace the environment value with the thread ID from the first task.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const threadId = process.env.AMP_THREAD_ID;
if (!threadId) throw new Error("AMP_THREAD_ID is required");
const repositoryDir = "/blaxel/work/repository";
function shellQuote(value: string): string {
return `'${value.replaceAll("'", `'"'"'`)}'`;
}
const resumed = await SandboxInstance.get("amp-sandbox");
const followUp = "Now identify the smallest useful test improvement. Do not change any files.";
let followUpOutput = "";
const followUpResult = await resumed.process.exec({
name: "amp-follow-up",
command: [
"amp",
"--mode low",
"--visibility private",
"--no-ide",
"--no-notifications",
"--no-color",
"--no-remote-control-terminal",
"--stream-json",
`--execute ${shellQuote(followUp)}`,
"threads continue",
shellQuote(threadId),
].join(" "),
workingDir: repositoryDir,
waitForCompletion: true,
timeout: 600,
onStdout: (chunk) => {
followUpOutput += chunk;
process.stdout.write(chunk);
},
onStderr: (chunk) => process.stderr.write(chunk),
});
const followUpResultEvent = followUpOutput
.split("\n")
.map((line) => {
try {
return JSON.parse(line);
} catch {
return undefined;
}
})
.find((event) => event?.type === "result");
if (
followUpResult.exitCode !== 0 ||
followUpResultEvent?.subtype !== "success" ||
followUpResultEvent?.is_error !== false
) {
throw new Error(`Amp thread ${threadId} did not finish successfully`);
}
```
```python Python theme={null}
import asyncio
import json
import os
import shlex
from blaxel.core import SandboxInstance
REPOSITORY_DIR = "/blaxel/work/repository"
async def main() -> None:
thread_id = os.environ.get("AMP_THREAD_ID")
if not thread_id:
raise RuntimeError("AMP_THREAD_ID is required")
resumed = await SandboxInstance.get("amp-sandbox")
follow_up = "Now identify the smallest useful test improvement. Do not change any files."
follow_up_output: list[str] = []
def handle_stdout(chunk: str) -> None:
follow_up_output.append(chunk)
print(chunk, end="")
follow_up_result = await resumed.process.exec(
{
"name": "amp-follow-up",
"command": " ".join(
[
"amp",
"--mode low",
"--visibility private",
"--no-ide",
"--no-notifications",
"--no-color",
"--no-remote-control-terminal",
"--stream-json",
f"--execute {shlex.quote(follow_up)}",
"threads continue",
shlex.quote(thread_id),
]
),
"working_dir": REPOSITORY_DIR,
"wait_for_completion": True,
"timeout": 600,
"on_stdout": handle_stdout,
"on_stderr": lambda chunk: print(chunk, end=""),
}
)
follow_up_result_event = None
for line in "".join(follow_up_output).splitlines():
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
if event.get("type") == "result":
follow_up_result_event = event
break
if (
follow_up_result.exit_code != 0
or not follow_up_result_event
or follow_up_result_event.get("subtype") != "success"
or follow_up_result_event.get("is_error") is not False
):
raise RuntimeError(f"Amp thread {thread_id} did not finish successfully")
if __name__ == "__main__":
asyncio.run(main())
```
Run the continuation script:
```shell TypeScript (npm) theme={null}
AMP_THREAD_ID=T-YOUR-THREAD-ID npx tsx continue.ts
```
```shell Python theme={null}
AMP_THREAD_ID=T-YOUR-THREAD-ID python continue.py
```
The sandbox wakes on the first process call. Its repository files and local Amp thread metadata remain available.
## 5. Open Amp in the sandbox terminal
Connect to the same sandbox when you want an interactive session:
```shell theme={null}
bl connect sandbox amp-sandbox
cd /blaxel/work/repository
amp --version
amp
```
## 6. Troubleshoot common failures
### Amp rejects the access token
Confirm that `AMP_API_KEY` contains an active Amp access token. Delete and recreate an existing sandbox after you rotate the token.
### The repository does not clone
Confirm that the sandbox can access the Git host. For a private repository, provide credentials through your approved secret workflow instead of placing them in the script.
### Amp cannot reach a required service
Allow the domains required by Amp, your Git host, and your dependency registries in the sandbox network policy.
Amp can also report `Network timeout` for a transient service failure. The process exits with a non-zero code and writes no structured events, so retry the same command before you treat the task as failed.
### A task exits without a successful result
Check the final structured `result` event. Amp can report an error in the event stream, so do not rely only on the process exit code.
### The thread does not continue
Reconnect to the same named sandbox and use the exact thread ID from the first task. A different sandbox does not contain the same repository state.
## 7. Delete the sandbox
Delete the sandbox when you finish:
```shell theme={null}
bl delete sandbox amp-sandbox
```
## Resources
Learn about Amp modes, threads, and command-line options.
Run and monitor more commands with Python or TypeScript.
# Run Astro in a sandbox
Source: https://docs.blaxel.ai/Tutorials/Astro
Run an Astro application inside a Blaxel sandbox and expose it securely through preview URLs, with setup steps and end-to-end code examples.
This tutorial explains how to run an Astro application inside a Blaxel sandbox and expose it securely using sandbox preview URLs.
## Prerequisites
Before starting, ensure you have:
* [Blaxel CLI](../cli-reference/introduction) installed and authenticated (`bl login`)
* Node.js 18+ installed
* `@blaxel/core` package installed in your project (`npm install @blaxel/core`)
## Architecture Overview
Running Astro inside a Blaxel sandbox requires a few adjustments:
* Configuring `astro.config.mjs` with `host: process.env.HOST` and `allowedHosts: true`
* Exposing the Astro dev server via a Blaxel preview URL
## Create a base sandbox image
### Dockerfile
```dockerfile theme={null}
FROM oven/bun:alpine
RUN apk update && apk add --no-cache \
git \
curl \
netcat-openbsd \
nodejs \
npm \
&& rm -rf /var/cache/apk/*
WORKDIR /app
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
# Create Astro project with npx (more reliable for template downloads), then use bun for deps
RUN npx create-astro@latest /app --template basics --no-install --no-git --yes \
&& bun install
COPY ./astro.config.mjs /app/astro.config.mjs
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
```
### astro.config.mjs
Create an `astro.config.mjs` file that allows external connections:
```javascript theme={null}
// @ts-check
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
server: {
host: process.env.HOST || '::',
port: 4321,
allowedHosts: true
}
});
```
### entrypoint.sh
Create an entrypoint script that starts the sandbox API and the dev server:
```bash theme={null}
#!/bin/sh
# Set environment variables
export PATH="/usr/local/bin:$PATH"
# Start sandbox-api in the background
/usr/local/bin/sandbox-api &
# Function to wait for port to be available
wait_for_port() {
local port=$1
local timeout=30
local count=0
echo "Waiting for port $port to be available..."
while ! nc -z localhost $port; do
sleep 1
count=$((count + 1))
if [ $count -gt $timeout ]; then
echo "Timeout waiting for port $port"
exit 1
fi
done
echo "Port $port is now available"
}
# Wait for port 8080 to be available
wait_for_port 8080
# Execute curl command to start Astro dev server
echo "Running Astro dev server..."
curl http://localhost:8080/process \
-X POST \
-H "Content-Type: application/json" \
-d '{
"name": "dev-server",
"workingDir": "/app",
"command": "bun run dev",
"waitForCompletion": false,
"restartOnFailure": true,
"maxRestarts": 25
}'
wait
```
### blaxel.toml
Create a `blaxel.toml` file in the same directory as your Dockerfile:
```toml theme={null}
type = "sandbox"
name = "astro-template"
[runtime]
memory = 4096
[[runtime.ports]]
name = "astro-dev"
target = 4321
protocol = "tcp"
```
## Deploy the sandbox image
Deploy the image by running:
```bash theme={null}
bl deploy
```
## Create or reuse a sandbox
Create a sandbox from the base image:
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-astro-sandbox";
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "astro",
},
image: "astro-template:latest",
memory: 4096,
});
```
## Configure CORS for preview URL access
Astro dev servers work well with permissive CORS headers when accessed through a preview URL:
```typescript theme={null}
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
```
Alternatively, you can use [custom domains](https://docs.blaxel.ai/Infrastructure/Custom-domains) to expose previews on your own domain.
## Create the preview URL
Astro runs on port 4321, so we expose that port via a [preview URL](../Sandboxes/Preview-url):
```typescript theme={null}
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "dev-server-preview" },
spec: {
responseHeaders,
public: false,
port: 4321,
},
});
```
## Generate a preview token
To securely access the preview, a token is required:
```typescript theme={null}
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24); // 1 day
const token = await preview.tokens.create(expiresAt);
```
## Start the dev server
If not using the entrypoint script, you can start the dev server programmatically:
```typescript theme={null}
async function startDevServer(sandbox: SandboxInstance) {
console.log("Starting Astro dev server...");
await sandbox.process.exec({
name: "dev-server",
command: "bun run dev",
workingDir: "/app",
waitForPorts: [4321],
restartOnFailure: true,
maxRestarts: 25,
});
}
```
## Stream logs
To monitor the Astro dev server output in real-time:
```typescript theme={null}
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log);
},
});
// When done monitoring, close the stream:
logStream.close();
```
## Access the Astro application
Once everything is running, the Astro application will be available at `https://?bl_preview_token=`.
## Complete example
Here is a full example combining all the steps:
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-astro-sandbox";
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
async function startDevServer(sandbox: SandboxInstance) {
await sandbox.process.exec({
name: "dev-server",
command: "bun run dev",
workingDir: "/app",
waitForPorts: [4321],
restartOnFailure: true,
maxRestarts: 25,
});
}
async function main() {
try {
// Create or reuse the sandbox
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "astro",
},
image: "astro-template:latest",
memory: 4096,
});
// Create preview
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "preview" },
spec: {
responseHeaders,
public: false,
port: 4321,
},
});
// Generate preview token
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
const token = await preview.tokens.create(expiresAt);
// Start dev server if not already running
const processes = await sandbox.process.list();
if (!processes.find((p) => p.name === "dev-server")) {
await startDevServer(sandbox);
}
// Print access URL
const webUrl = `${preview.spec?.url}?bl_preview_token=${token.value}`;
console.log(`Astro Preview URL: ${webUrl}`);
// Stream logs
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log)
},
});
// Keep running until interrupted
process.on("SIGINT", () => {
logStream.close();
process.exit(0);
});
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();
```
# Deploy Claude Agent SDK agents on Blaxel
Source: https://docs.blaxel.ai/Tutorials/Claude-Agent-SDK
Deploy a Claude Agent SDK agent to Blaxel Agents Hosting as a serverless auto-scalable API, and colocate it close to the sandboxes it works on.
[Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview) is a library for building autonomous AI agents using Claude Code. You can deploy your Claude Agent SDK projects to Blaxel with minimal code editing (and zero configuration), enabling you to colocate them close to the [sandboxes](/Sandboxes/Overview) the agents work on.
## Prerequisites
* An Anthropic API key, required by Claude Agent SDK. If not, [sign up for an Anthropic account](https://platform.claude.com/) and obtain an API key.
* A Blaxel account. If not, [sign up for a Blaxel account](https://blaxel.ai).
## 1. Install the Blaxel CLI and log in to Blaxel
The main way to deploy an agent on Blaxel is with the [Blaxel CLI](../cli-reference/introduction). This method is detailed in this tutorial. Alternatively you can [connect a GitHub repository](/Agents/Github-integration) - any push to the `main` branch of the repository will automatically update the deployment on Blaxel - or deploy from a variety of pre-built templates using the Blaxel Console.
[Install the Blaxel CLI](../cli-reference/introduction) and log in to Blaxel using this command:
```bash theme={null}
bl login
```
## 2. Install required dependencies
Create a directory for the project:
```shell theme={null}
mkdir assistant-agent && cd assistant-agent
```
The best way to deploy this project is through [Blaxel Agents Hosting](/Agents/Overview), which uses the same underlying infrastructure as sandboxes. Agents deployed on Blaxel Agents Hosting must expose an HTTP endpoint for requests.
In your project directory, install the [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview) for the agent loop and [Express](https://expressjs.com/) (TypeScript) / [FastAPI](https://fastapi.tiangolo.com/) (Python) to handle HTTP requests and responses:
```shell TypeScript (npm) theme={null}
npm init # if new project
npm install @anthropic-ai/claude-agent-sdk express tsx typescript
```
```shell TypeScript (pnpm) theme={null}
pnpm init # if new project
pnpm install @anthropic-ai/claude-agent-sdk express tsx typescript
```
```shell TypeScript (yarn) theme={null}
yarn init # if new project
yarn add @anthropic-ai/claude-agent-sdk express tsx typescript
```
```shell TypeScript (bun) theme={null}
bun init -m --yes # if new project
bun add @anthropic-ai/claude-agent-sdk express tsx typescript
```
```shell Python (pip) theme={null}
python3 -m venv .venv && source .venv/bin/activate # if new project
pip install claude-agent-sdk "fastapi[standard]"
```
## 3. Configure the environment
Add your Anthropic API key to a `.env` file in the project directory:
```shell theme={null}
echo "ANTHROPIC_API_KEY=your_key_here" > .env
```
## 4. Build the agent
In your project directory, create a file named `index.ts` (TypeScript) or `main.py` (Python) with the following code. Otherwise, [this other code](/Tutorials/Claude-Agent-SDK-MCP) shows how to **equip the agent with its own [Blaxel Sandbox](/Sandboxes/Overview).**
```typescript TypeScript theme={null}
import { query } from "@anthropic-ai/claude-agent-sdk";
import express from "express";
const host = process.env.HOST || "::";
const port = parseInt(process.env.PORT || "8000");
const app = express();
app.use(express.json());
app.post("/query", async (req, res) => {
const { prompt } = req.body;
if (!prompt) {
return res.status(400).json({ error: "prompt is required" });
}
try {
let response = "";
for await (const message of query({
prompt,
options: {
maxTurns: 1
}
})) {
if (message.type === "result") {
response = message.result;
break;
}
}
return res.json({ response });
} catch (error) {
return res.status(500).json({ error: error instanceof Error ? error.message : "Unknown error" });
}
});
app.listen(port, host, () => {
console.log(`Server listening on ${host}:${port}`);
});
```
```python Python theme={null}
import os
from fastapi import FastAPI, HTTPException, Request
from claude_agent_sdk import query, ClaudeAgentOptions
import uvicorn
host = os.getenv("HOST", "::")
port = int(os.getenv("PORT", "8000"))
app = FastAPI()
@app.post("/query")
async def query_endpoint(request: Request):
body = await request.json()
prompt = body.get("prompt")
if not prompt:
raise HTTPException(status_code=400, detail="prompt is required")
try:
response = ""
async for message in query(
prompt=prompt,
options=ClaudeAgentOptions(max_turns=1)
):
if hasattr(message, "result"):
response = message.result
break
return {"response": response}
except Exception as error:
raise HTTPException(
status_code=500,
detail=str(error) if error else "Unknown error"
)
if __name__ == "__main__":
print(f"Server listening on {host}:{port}")
uvicorn.run(app, host=host, port=port)
```
This creates a simple single-message agent with the Claude Agent SDK and an endpoint at `/query` to accept requests.
The agent's HTTP service must be bound to the host and port provided by Blaxel. Blaxel automatically injects these values as `HOST` and `PORT` variables into the runtime environment. It is important to read these variables in your code and ensure that the agent's HTTP service binds to the correct host and port.
In TypeScript, entrypoints are managed in the `scripts` section of the `package.json` file. Update your `package.json` to ensure that `start` and `dev` scripts are defined in the `scripts` section (TypeScript only).
```json TypeScript (npm/pnpm/yarn) theme={null}
{
"scripts": {
"start": "tsx index.ts",
"dev": "tsx --watch index.ts",
"build": "tsc"
},
// ...
}
```
```json TypeScript (bun) theme={null}
{
"scripts": {
"start": "bun run index.ts",
"dev": "bun --watch index.ts"
},
// ...
}
```
## 5. Enable telemetry (optional)
Instrumentation happens automatically when workloads run on Blaxel. To enable telemetry:
* Add the required package to your project:
```shell TypeScript (npm) theme={null}
npm install @blaxel/telemetry
```
```shell TypeScript (pnpm) theme={null}
pnpm install @blaxel/telemetry
```
```shell TypeScript (yarn) theme={null}
yarn add @blaxel/telemetry
```
```shell TypeScript (bun) theme={null}
bun install @blaxel/telemetry
```
```shell Python (pip) theme={null}
pip install "blaxel[telemetry]"
```
* Import the package in your code:
```typescript TypeScript theme={null}
import "@blaxel/telemetry";
```
```python Python theme={null}
import blaxel.telemetry
```
## 6. Test the agent locally
Test the agent by making the endpoint available locally:
```shell theme={null}
bl serve --hotreload &
```
This starts the agent locally while sandboxing the core agent logic, function calls and model API calls exactly as they would be when deployed on Blaxel. The `--hotreload` flag monitors and reloads the agent if the source code changes.
Note the host port on which the agent is running.
In another terminal, send the agent a request (update the endpoint URL below with the correct port number for your agent):
```shell theme={null}
curl -X POST http://0.0.0.0:1338/query \
-H "Content-Type: application/json" \
-d '{"prompt": "what is your name?"}'
```
You should see output similar to the following:
```shell theme={null}
{"response":"I'm Claude, an AI assistant made by Anthropic. I'm here to help you with coding tasks, file operations, terminal commands, and many other development-related activities. How can I help you today?"}
```
## 7. Deploy the agent on Blaxel
You're now ready to deploy the agent on Blaxel. When deploying to Blaxel, your workloads are served optimally to dramatically accelerate cold-start and latency while enforcing your deployment policies.
Deploying the agent is as simple as running the following command:
```shell theme={null}
bl deploy
```
The Blaxel CLI will prompt for the type of resource you are deploying. It will then auto-detect other details of your project and begin the deployment. The deployment process usually takes a few minutes, and you can watch progress from the Blaxel Console.
Blaxel uses its default `Dockerfile` to build your agent. If you already have a `Dockerfile` in the root of your project directory, Blaxel will automatically use that instead. You can set custom parameters for an agent deployment (for example, the agent name, memory, environment, or custom entrypoint) by adding an optional [`blaxel.toml file`](/deployment-reference) in the root of your project directory.
Once the deployment process is complete, log in to the [Blaxel Console](https://app.blaxel.ai/) to find the global endpoint for your agent service. Typically, this will be of the form `https://run.blaxel.ai/WORKSPACE/agents/AGENT`.
## 8. Test the agent on Blaxel
By default, agents deployed on Blaxel are not public. All agent requests must be authenticated via a [bearer token](../Security/Access-tokens). Requests can be made either via the Blaxel API or the Blaxel CLI.
Test the deployed agent by sending an authenticated request to its global endpoint (update the endpoint URL below with the correct endpoint URL for your agent, and modify the `prompt` as desired):
```bash theme={null}
curl -X POST https://run.blaxel.ai/$(bl workspace --current)/agents/assistant-agent/query \
-H "X-Blaxel-Workspace: $(bl workspace --current)" \
-H "X-Blaxel-Authorization: Bearer $(bl token)" \
-H "Content-Type: application/json" \
-d '{"prompt": "what is your name?"}'
```
You should see a response from the agent to your query.
You can also send a request through the Blaxel CLI:
```shell theme={null}
bl run agent assistant-agent/query --data '{"prompt":"what is your name?"}'
```
Although deployed agents are private by default, it is possible to [make an agent publicly available](/Agents/Query-agents#make-an-agent-public).
That's it! You're ready to start building and deploying agents with Claude Agent SDK on Blaxel.
## Resources
Want more info on developing and deploying agents on Blaxel? Check out the following resources:
Build an agent with Claude Agent SDK that can operate a Blaxel sandbox using MCP.
Deploy a self-hosted Claude Managed Agents environment on Blaxel.
Complete tutorial for using the TypeScript SDK to develop an agent using Blaxel services.
Complete tutorial for using the Python SDK to develop an agent using Blaxel services.
Complete tutorial for deploying AI agents to Blaxel.
Complete tutorial for managing variables and secrets when deploying to Blaxel.
# Use Claude Agent SDK with MCP Code Mode
Source: https://docs.blaxel.ai/Tutorials/Claude-Agent-SDK-Code-Mode
Connect the Claude Agent SDK to a Blaxel MCP server running in code mode to call any OpenAPI-described API by executing TypeScript in a sandbox.
Blaxel supports "code mode" natively, enabling a more efficient way to execute tool calls over MCP. Essentially, code mode turns any OpenAPI specification into a two-tool MCP server: search lets an AI agent explore the API spec, and execute runs JavaScript code in a sandbox to call the actual API.
This tutorial demonstrates by deploying an API as an MCP server on Blaxel and using an agent to work with it. It uses the [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview), but you could also use other frameworks like LangChain, Vercel AI SDK, Mastra, or your own custom code.
For more information on "code mode" or "code execution with MCP", refer to blog posts by [Cloudflare](https://blog.cloudflare.com/code-mode/) and [Anthropic](https://www.anthropic.com/engineering/code-execution-with-mcp).
## Prerequisites
* An Anthropic API key, required by Claude Agent SDK. If not, [sign up for an Anthropic account](https://platform.claude.com/) and obtain an API key.
* A Blaxel account and API key. If not, [sign up for a Blaxel account](https://blaxel.ai) and [obtain an API key](/Security/Access-tokens#api-keys).
* The Blaxel CLI. If not, [download and install the Blaxel CLI](../cli-reference/introduction).
## 1. Install required dependencies
In your project directory, install the [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview) for the agent loop and the Blaxel TypeScript SDK / Python SDK for sandbox operations:
```shell TypeScript (npm) theme={null}
npm init # if new project
npm install @anthropic-ai/claude-agent-sdk @blaxel/core
```
```shell TypeScript (pnpm) theme={null}
pnpm init # if new project
pnpm install @anthropic-ai/claude-agent-sdk @blaxel/core
```
```shell TypeScript (yarn) theme={null}
yarn init # if new project
yarn add @anthropic-ai/claude-agent-sdk @blaxel/core
```
```shell TypeScript (bun) theme={null}
bun init -m --yes # if new project
bun install @anthropic-ai/claude-agent-sdk @blaxel/core
```
```shell Python (pip) theme={null}
python3 -m venv .venv && source .venv/bin/activate # if new project
pip install claude-agent-sdk blaxel
```
Export your API keys to the local environment:
```shell theme={null}
export ANTHROPIC_API_KEY=
export BL_API_KEY=
```
## 2. Build and deploy an MCP server in code mode
This tutorial creates and deploys an MCP server for the example [Petstore v2 API](https://petstore.swagger.io/).
In your project directory, create and run a file named `setup.ts` (TypeScript) or `setup.py` (Python) with the following code, or use the Blaxel CLI command shown, to deploy the API on Blaxel.
```typescript TypeScript theme={null}
import { createFunction } from "@blaxel/core";
async function main() {
await createFunction({
body: {
metadata: {
name: "petstore-code-mode",
displayName: "Petstore Code Mode",
},
spec: {
runtime: {
image: "blaxel/code-mode:latest",
memory: 2048,
envs: [
{ name: "OPENAPI_REFERENCE", value: "https://petstore.swagger.io/v2/swagger.json" },
],
},
},
},
});
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
```
```python Python theme={null}
import asyncio
import sys
from blaxel.core.client import client
from blaxel.core.client.api.functions.create_function import asyncio as create_function
from blaxel.core.client.models.env import Env
from blaxel.core.client.models.function import Function
from blaxel.core.client.models.function_runtime import FunctionRuntime
from blaxel.core.client.models.function_spec import FunctionSpec
from blaxel.core.client.models.metadata import Metadata
async def main():
await create_function(
client=client,
body=Function(
metadata=Metadata(
name="petstore-code-mode",
display_name="Petstore Code Mode",
),
spec=FunctionSpec(
runtime=FunctionRuntime(
image="blaxel/code-mode:latest",
memory=2048,
envs=[
Env(
name="OPENAPI_REFERENCE",
value="https://petstore.swagger.io/v2/swagger.json",
),
],
),
),
),
)
asyncio.run(main())
```
```bash Blaxel CLI theme={null}
bl apply -f - <
Check the deployment status:
```bash theme={null}
bl get mcp petstore-code-mode
```
Once the status shows `DEPLOYED`, any agent can use `petstore-code-mode` as a tool provider.
## 3. Build the agent
Obtain the MCP server URL with the following command:
```bash theme={null}
bl get mcp petstore-code-mode -o json | jq -r '.[] | .metadata.url'
```
In your project directory, create a file named `index.ts` (TypeScript) or `main.py` (Python) with the following code. Replace the placeholder with the MCP server URL from the previous command.
```typescript TypeScript theme={null}
import { query } from "@anthropic-ai/claude-agent-sdk";
const blApiKey = process.env.BL_API_KEY;
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
const prompt = process.argv[2];
if (!blApiKey) {
console.error("Error: BL_API_KEY environment variable is not set");
process.exit(1);
}
if (!anthropicApiKey) {
console.error("Error: ANTHROPIC_API_KEY environment variable is not set");
process.exit(1);
}
if (!prompt) {
console.error("Error: prompt argument is required");
process.exit(1);
}
for await (const message of query({
prompt: prompt,
options: {
mcpServers: {
petstore: {
type: "http",
url: "/mcp",
headers: { "Authorization": `Bearer ${blApiKey}` },
},
},
allowedTools: ["mcp__petstore__*"],
},
})) {
if (message.type === "system" && message.subtype === "init") {
for (const s of message.mcp_servers) {
console.log(`MCP server "${s.name}": ${s.status}`);
}
}
if (message.type === "assistant") {
for (const block of message.message.content) {
if (block.type === "tool_use") {
console.log(`\n--- ${block.name} ---`);
console.log(` input: ${JSON.stringify(block.input).slice(0, 200)}`);
}
if (block.type === "text") {
console.log(` text: ${block.text.slice(0, 300)}${block.text.length > 300 ? "…" : ""}`);
}
}
}
if (message.type === "result" && message.subtype === "success") {
console.log(`\n${message.result}`);
}
if (message.type === "result" && message.subtype !== "success") {
console.error("Error:", message);
}
}
```
```python Python theme={null}
import asyncio
import json
import os
import sys
from claude_agent_sdk import (
query,
ClaudeAgentOptions,
SystemMessage,
AssistantMessage,
ResultMessage,
TextBlock,
ToolUseBlock,
)
bl_api_key = os.environ.get("BL_API_KEY")
anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
prompt = sys.argv[1] if len(sys.argv) > 1 else None
if not bl_api_key:
print("Error: BL_API_KEY environment variable is not set", file=sys.stderr)
sys.exit(1)
if not anthropic_api_key:
print("Error: ANTHROPIC_API_KEY environment variable is not set", file=sys.stderr)
sys.exit(1)
if not prompt:
print("Error: prompt argument is required", file=sys.stderr)
sys.exit(1)
async def main():
async for message in query(
prompt=prompt,
options=ClaudeAgentOptions(
mcp_servers={
"petstore": {
"type": "http",
"url": "/mcp",
"headers": {"Authorization": f"Bearer {bl_api_key}"},
}
},
allowed_tools=["mcp__petstore__*"],
),
):
if isinstance(message, SystemMessage) and message.subtype == "init":
for s in message.data.get("mcp_servers", []):
print(f'MCP server "{s["name"]}": {s["status"]}')
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, ToolUseBlock):
print(f"\n--- {block.name} ---")
print(f" input: {json.dumps(block.input)[:200]}")
if isinstance(block, TextBlock):
text = block.text
print(f" text: {text[:300]}{'…' if len(text) > 300 else ''}")
if isinstance(message, ResultMessage) and message.subtype == "success":
print(f"\n{message.result}")
if isinstance(message, ResultMessage) and message.subtype != "success":
print("Error:", message, file=sys.stderr)
asyncio.run(main())
```
## 4. Test the agent
Send the agent a query:
```typescript TypeScript (npm/pnpm/yarn) theme={null}
tsx index.ts "list all available pets"
```
```typescript TypeScript (Bun) theme={null}
bun index.ts "list all available pets"
```
```python Python theme={null}
python main.py "list all available pets"
```
You should see the agent using the two available tools, first searching for the correct API endpoint and then writing and executing code to query the API and respond to your prompt.
In case of errors, you can view the logs of the deployed MCP server with the command below:
```bash theme={null}
bl logs mcp petstore-code-mode
```
The following resources will help you go further:
Complete tutorial for building an agent with Claude Agent SDK and deploying it on Blaxel as a serverless auto-scalable API.
Complete tutorial for using the TypeScript SDK to develop an agent using Blaxel services.
Complete tutorial for using the Python SDK to develop an agent using Blaxel services.
Complete tutorial for deploying AI agents to Blaxel.
# Use Claude Agent SDK with Blaxel
Source: https://docs.blaxel.ai/Tutorials/Claude-Agent-SDK-Index
Deploy Claude Agent SDK agents on Blaxel with serverless hosting, colocated close to sandboxes for near-instant latency and full observability.
Anthropic's [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview) is a library for building autonomous AI agents using Claude Code. You can deploy your Claude Agent SDK projects to Blaxel with minimal code editing (and zero configuration), enabling you to colocate them close to the [sandboxes](/Sandboxes/Overview) the agents work on.
Build an agent that operates a sandbox using the sandbox's MCP server and Claude Agent SDK.
Build an agent that connects to a Blaxel MCP server running in code mode using Claude Agent SDK.
Build and deploy entire Claude Agent SDK agents to Blaxel.
Deploy a self-hosted Claude Managed Agents environment on Blaxel.
# Connect Claude Agent SDK to sandboxes
Source: https://docs.blaxel.ai/Tutorials/Claude-Agent-SDK-MCP
Connect the Claude Agent SDK (formerly Claude Code SDK) to a Blaxel sandbox to operate processes, files, and code through its built-in MCP server.
Every Blaxel sandbox exposes a Model Context Protocol (MCP) server that allows agents to operate the sandbox using tool calls. This includes [tools for process management, filesystem operations, and code generation](../Sandboxes/MCP#tools-available-in-the-mcp-server).
This tutorial demonstrates by creating a sandbox-aware agent using the [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview), but you could also use other frameworks like LangChain, Vercel AI SDK, Mastra, or your own custom code.
## Prerequisites
* An Anthropic API key, required by Claude Agent SDK. If not, [sign up for an Anthropic account](https://platform.claude.com/) and obtain an API key.
* A Blaxel account and API key. If not, [sign up for a Blaxel account](https://blaxel.ai) and [obtain an API key](/Security/Access-tokens#api-keys).
* The Blaxel CLI. If not, [download and install the Blaxel CLI](../cli-reference/introduction).
## 1. Install required dependencies
Create a directory for the project:
```shell theme={null}
mkdir sandbox-agent && cd sandbox-agent
```
Agents deployed on Blaxel must expose an HTTP endpoint for requests.
In your project directory, install the [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview) for the agent loop, the Blaxel TypeScript SDK / Python SDK for sandbox operations, and [Express](https://expressjs.com/) (TypeScript) / [FastAPI](https://fastapi.tiangolo.com/) (Python) to handle HTTP requests and responses:
```shell TypeScript (npm) theme={null}
npm init # if new project
npm install @anthropic-ai/claude-agent-sdk express @blaxel/core
```
```shell TypeScript (pnpm) theme={null}
pnpm init # if new project
pnpm install @anthropic-ai/claude-agent-sdk express @blaxel/core
```
```shell TypeScript (yarn) theme={null}
yarn init # if new project
yarn add @anthropic-ai/claude-agent-sdk express @blaxel/core
```
```shell TypeScript (bun) theme={null}
bun init -m --yes # if new project
bun install @anthropic-ai/claude-agent-sdk express @blaxel/core
```
```shell Python (pip) theme={null}
python3 -m venv .venv && source .venv/bin/activate # if new project
pip install claude-agent-sdk "fastapi[standard]" blaxel
```
## 2. Configure the environment
Add your API keys to a `.env` file in the project directory:
```shell theme={null}
echo "ANTHROPIC_API_KEY=your_anthropic_key_here" > .env
echo "BL_API_KEY=your_blaxel_key_here" >> .env
```
## 3. Build the agent
In your project directory, create a file named `index.ts` (TypeScript) or `main.py` (Python) with the following code:
```typescript TypeScript theme={null}
import { query } from "@anthropic-ai/claude-agent-sdk";
import express from "express";
import { SandboxInstance } from "@blaxel/core";
const host = process.env.HOST || "::";
const port = parseInt(process.env.PORT || "8000");
const app = express();
app.use(express.json());
app.post("/query", async (req, res) => {
const { prompt } = req.body;
if (!prompt) {
return res.status(400).json({ error: "prompt is required" });
}
if (!process.env.BL_API_KEY) {
return res.status(400).json({ error: "BL_API_KEY env var is required" });
}
try {
const sandbox = await SandboxInstance.createIfNotExists({
name: "my-sandbox",
image: "blaxel/base-image:latest",
memory: 4096,
region: "us-pdx-1",
});
let response = "";
for await (const message of query({
prompt: prompt,
options: {
systemPrompt: "You are connected to a sandbox environment with tools. Use the tools to accomplish the task.",
mcpServers: {
"sandbox": {
type: "http",
url: `${sandbox.metadata?.url}/mcp`,
headers: {
Authorization: `Bearer ${process.env.BL_API_KEY}`,
},
},
},
tools: [],
permissionMode: "bypassPermissions",
allowDangerouslySkipPermissions: true,
}
})) {
if (message.type === "assistant" && message.message?.content) {
for (const block of message.message.content) {
if ("text" in block) {
console.log(block.text);
} else if ("name" in block) {
console.log(`Tool: ${block.name}`);
}
}
} else if (message.type === "result") {
console.log(`Done: ${message.result}`); // Final result
response = message.result
}
}
return res.json({ response });
} catch (error) {
return res.status(500).json({ error: error instanceof Error ? error.message : "Unknown error" });
}
});
app.listen(port, host, () => {
console.log(`Server listening on ${host}:${port}`);
});
```
```python Python theme={null}
import os
from fastapi import FastAPI, HTTPException, Request
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ResultMessage
from blaxel.core import SandboxInstance
import uvicorn
host = os.getenv("HOST", "::")
port = int(os.getenv("PORT", "8000"))
app = FastAPI()
@app.post("/query")
async def query_endpoint(request: Request):
body = await request.json()
prompt = body.get("prompt")
if not prompt:
raise HTTPException(status_code=400, detail="prompt is required")
api_key = os.getenv("BL_API_KEY")
if not api_key:
raise HTTPException(status_code=400, detail="BL_API_KEY env var is required")
try:
sandbox = await SandboxInstance.create_if_not_exists({
"name": "my-sandbox",
"image": "blaxel/base-image:latest",
"memory": 4096,
"region": "us-pdx-1",
})
response = ""
async for message in query(
prompt=prompt,
options=ClaudeAgentOptions(
system_prompt="You are connected to a sandbox environment with tools. Use the tools to accomplish the task.",
mcp_servers={
"sandbox": {
"type": "http",
"url": f"{sandbox.metadata.url}/mcp",
"headers": {
"Authorization": f"Bearer {api_key}"
}
}
},
tools=[],
permission_mode="bypassPermissions",
)
):
if isinstance(message, AssistantMessage):
for block in message.content:
if hasattr(block, "text"):
print(block.text)
elif hasattr(block, "name"):
print(f"Tool: {block.name}")
elif isinstance(message, ResultMessage):
print(f"Done: {message.subtype}")
response = message.result
return {"response": response}
except Exception as error:
raise HTTPException(
status_code=500,
detail=str(error) if error else "Unknown error"
)
if __name__ == "__main__":
print(f"Server listening on {host}:{port}")
uvicorn.run(app, host=host, port=port)
```
This creates a Blaxel sandbox named `my-sandbox` and an agent using the Claude Agent SDK.
* The sandbox exposes a streamable [HTTP MCP server](/Sandboxes/MCP) at the sandbox's base URL: `https:///mcp`. The base URL can be [retrieved](https://docs.blaxel.ai/api-reference/compute/get-sandbox) under `metadata.url` in the sandbox.
* The agent exposes an HTTP endpoint at `/query` to accept user requests.
* The agent's HTTP service is bound to the host and port provided by Blaxel. Blaxel automatically injects these values as `HOST` and `PORT` variables into the runtime environment.
* The agent configuration includes the sandbox MCP server URL and uses the Blaxel API key as a credential to gain access to it (the `Authorization` header).
In TypeScript, entrypoints are managed in the `scripts` section of the `package.json` file. Update your `package.json` to ensure that `start` and `dev` scripts are defined in the `scripts` section (TypeScript only).
```json TypeScript (npm/pnpm/yarn) theme={null}
{
"scripts": {
"start": "tsx index.ts",
"dev": "tsx --watch index.ts",
"build": "tsc"
},
// ...
}
```
```json TypeScript (bun) theme={null}
{
"scripts": {
"start": "bun run index.ts",
"dev": "bun --watch index.ts"
},
// ...
}
```
Your agent is now ready to operate the sandbox using the sandbox's MCP tools! For example, you could instruct the agent to "install a Python dev environment" and it would use the available MCP tools to find, download and install all the required libraries and tools for Python development in the sandbox.
## Next steps
Blaxel isn't just a sandbox platform. It also lets you [co-host and deploy your agent](../Agents/Overview) as a serverless auto-scalable endpoint, with near-instant latency. Agent hosting provides endpoints for both synchronous and asynchronous requests and includes full observability and tracing out of the box.
The following resources will help you go further:
Complete tutorial for building an agent with Claude Agent SDK and deploying it on Blaxel as a serverless auto-scalable API.
Complete tutorial for using the TypeScript SDK to develop an agent using Blaxel services.
Complete tutorial for using the Python SDK to develop an agent using Blaxel services.
Complete tutorial for deploying AI agents to Blaxel.
Complete tutorial for managing variables and secrets when deploying to Blaxel.
# Run Claude Code in a sandbox
Source: https://docs.blaxel.ai/Tutorials/Claude-Code
Run Claude Code inside a Blaxel sandbox to execute coding tasks on a hosted codebase, with persistent storage and secure network access.
This tutorial explains how to run [Claude Code](https://claude.com/product/claude-code) inside a Blaxel sandbox and have it execute coding tasks on a codebase hosted in a different sandbox.
## Prerequisites
Before starting, ensure you have:
* a Python or TypeScript development environment;
* a Blaxel account and API key. If not, [sign up for a Blaxel account](https://blaxel.ai) and [create a Blaxel API key](../Security/Access-tokens#api-keys);
* a Claude account with an active subscription or an Anthropic Console account with usage-based billing, required by Claude Code. If not, [sign up for an Anthropic account](https://platform.claude.com/) and obtain a subscription or API credits as required.
If you wish to use an Anthropic API key instead of logging in to your Claude or Anthropic Console account, you can still follow this tutorial but you will need to perform [alternative steps for API key recognition when starting Claude Code](https://github.com/anthropics/claude-code/issues/441).
## Install the Blaxel CLI and SDK
1. [Download and install the Blaxel CLI](https://docs.blaxel.ai/cli-reference/introduction#install) and log in to your Blaxel account:
```shell theme={null}
bl login
```
2. In a new directory, install the Blaxel SDK ([Python](https://github.com/blaxel-ai/sdk-python) and [TypeScript](https://github.com/blaxel-ai/sdk-typescript) are both supported):
```shell TypeScript (npm) theme={null}
npm init # if new project
npm install @blaxel/core
```
```shell TypeScript (pnpm) theme={null}
pnpm init # if new project
pnpm install @blaxel/core
```
```shell TypeScript (yarn) theme={null}
yarn init # if new project
yarn add @blaxel/core
```
```shell TypeScript (bun) theme={null}
bun init -m --yes # if new project
bun install @blaxel/core
```
```shell Python theme={null}
python3 -m venv .venv
source .venv/bin/activate
pip install blaxel
```
## Create sandboxes
1. In the host environment, define the following variable:
```shell theme={null}
export BLAXEL_API_KEY=YOUR-BLAXEL-API-KEY-HERE
```
2. Create a script named `main.py` (Python) or `index.ts` (TypeScript) in the same directory.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
async function main() {
// Check for required variables
const blaxelApiKey = process.env.BLAXEL_API_KEY;
if (!blaxelApiKey) {
throw new Error("BLAXEL_API_KEY environment variable is not set");
}
// Create application sandbox
const appSandbox = await SandboxInstance.createIfNotExists({
name: "nextjs-sandbox",
image: "blaxel/nextjs:latest",
memory: 4096,
region: "us-pdx-1",
});
console.log("Application sandbox created");
// Start dev server in application sandbox
await appSandbox.process.exec({
workingDir: "/blaxel/app",
command: "npm run dev -- --hostname $HOST --port 3000"
});
console.log("Application server started");
// Create preview URL
const appPreview = await appSandbox.previews.createIfNotExists({
metadata: { name: "app-preview" },
spec: {
port: 3000,
public: false,
}
});
console.log("Preview URL created");
// Create preview token
// Valid for 24 hours
const expiresAt = new Date(Date.now() + 1440 * 60 * 1000);
const token = await appPreview.tokens.create(expiresAt);
// Get preview URL
console.log(`Preview URL: ${appPreview.spec.url}?bl_preview_token=${token.value}`);
console.log(`MCP URL: ${appSandbox.metadata.url}/mcp`);
// Create Claude Code sandbox
const claudeSandbox = await SandboxInstance.createIfNotExists({
name: "claude-sandbox",
image: "blaxel/claude-code:latest",
memory: 4096,
region: "us-pdx-1",
envs: [
{ name: "BLAXEL_API_KEY", value: blaxelApiKey }
]
});
console.log("Claude Code sandbox created");
}
main();
```
```python Python theme={null}
import asyncio
import os
import sys
from datetime import datetime, timedelta, UTC
from blaxel.core import SandboxInstance
async def main():
# Check for required variables
blaxel_api_key = os.getenv("BLAXEL_API_KEY")
if not blaxel_api_key:
raise ValueError("BLAXEL_API_KEY environment variable is not set")
# Create application sandbox
app_sandbox = await SandboxInstance.create_if_not_exists({
"name": "nextjs-sandbox",
"image": "blaxel/nextjs:latest",
"memory": 4096,
"region": "us-pdx-1",
})
print("Application sandbox created")
# Start dev server in application sandbox
await app_sandbox.process.exec({
"working_dir": "/blaxel/app",
"command": "npm run dev -- --hostname $HOST --port 3000"
})
print("Application server started")
# Create preview URL
app_preview = await app_sandbox.previews.create_if_not_exists({
"metadata": {"name": "app-preview"},
"spec": {
"port": 3000,
"public": False,
}
})
print("Preview URL created")
# Create preview token
# Valid for 24 hours
expires_at = datetime.now(UTC) + timedelta(minutes=1440)
token = await app_preview.tokens.create(expires_at)
# Get preview URL
print(f"Preview URL: {app_preview.spec.url}?bl_preview_token={token.value}")
print(f"MCP URL: {app_sandbox.metadata.url}/mcp")
# Create Claude Code sandbox
claude_sandbox = await SandboxInstance.create_if_not_exists({
"name": "claude-sandbox",
"image": "blaxel/claude-code:latest",
"memory": 4096,
"region": "us-pdx-1",
"envs": [
{"name": "BLAXEL_API_KEY", "value": blaxel_api_key}
]
})
print("Claude Code sandbox created")
if __name__ == "__main__":
asyncio.run(main())
```
This script creates two Blaxel sandboxes:
* `claude-sandbox` using Blaxel's Claude Code image
* `nextjs-sandbox` using Blaxel's Next.js base image
In the Claude Code sandbox, it:
* uses the `blaxel/claude-code:latest` image, which includes Claude Code and the sandbox API;
* adds the Blaxel API key to `claude-sandbox` as an environment variable named `BLAXEL_API_KEY`.
In the application sandbox, it:
* starts the Next.js dev server in `nextjs-sandbox` on port 3000;
* creates a preview URL for the Next.js service running in `nextjs-sandbox` on port 3000;
* creates an access token for the preview URL, valid for 24 hours;
* returns the preview URL.
3. Run the script to create the sandboxes and preview URL:
```python Python theme={null}
python main.py
```
```typescript TypeScript theme={null}
bun index.ts
```
Once complete, the script displays the generated preview URL for the Next.js application (for example, `https://b186....preview.bl.run?bl_preview_token=cbba622560db78e...`) and the MCP server URL (for example, `https://sbx-nextjs-sandbox....bl.run/mcp`) for the Next.js sandbox. Note these values, as you will require them in subsequent steps.
## Configure Claude Code
1. Connect to the Claude Code sandbox terminal:
```shell theme={null}
bl connect sandbox claude-sandbox
```
2. Confirm Claude Code is available in the sandbox:
```shell theme={null}
claude --version
```
The `blaxel/claude-code:latest` image includes Claude Code on `PATH`.
3. Add the application sandbox's MCP server URL (obtained from the sandbox creation script in the previous section) to Claude Code:
```shell theme={null}
claude mcp add --transport http sandbox YOUR-SANDBOX-MCP-URL-HERE --header "Authorization: Bearer $BLAXEL_API_KEY"
```
4. Confirm that Claude Code is able to connect to the application sandbox's MCP server. Run the following command and confirm that you see output like `sandbox: .... connected`:
```shell theme={null}
claude mcp list
```
Claude Code is now ready to operate the sandbox using the sandbox's MCP tools.
## Test Claude Code
Start Claude Code in the sandbox:
```shell theme={null}
claude
```
You will be prompted for authentication, theme selection and permissions.
If you wish to use an Anthropic API key instead of logging in to your Claude or Anthropic Console account, refer to the [alternative steps for API key recognition when starting Claude Code](https://github.com/anthropics/claude-code/issues/441).
Once these steps are completed, give Claude Code a coding task referencing the application sandbox, as in the example prompt below:
```shell theme={null}
You have access to a sandbox environment over MCP. The sandbox includes tools to read and write files and directories and run commands. The sandbox includes a skeleton Next.js application at /blaxel/app. Update the application codebase and complete the coding task below.
You must make all your changes only in the sandbox.
Do not make any changes in the local environment.
Your task is: create a website for a new board game of your own invention, including an interactive demo
```
Claude Code will connect to the application sandbox, inspect the Next.js codebase and make changes as per your request. You may be prompted for permissions to use the sandbox MCP tools during the process. Once complete, visit the application sandbox's preview URL (obtained from the sandbox creation script in the previous section) to see the result.
## Resources
Want more information on building and deploying with Claude on Blaxel? Check out the following resources:
Build and deploy Claude Agent SDK agents on Blaxel.
An agent that operates a sandbox using the sandbox's MCP server and Claude Agent SDK.
Build an agent that connects to a Blaxel MCP server running in code mode using Claude Agent SDK.
Deploy a self-hosted Claude Managed Agents environment on Blaxel.
Complete tutorial for deploying AI agents on Blaxel.
# Self-host Claude Managed Agents on Blaxel
Source: https://docs.blaxel.ai/Tutorials/Claude-Managed-Agents
Self-host the Claude Managed Agents (CMA) sandbox execution layer on Blaxel while Anthropic runs the agent loop, with isolated tool execution.
Blaxel integrates with [Claude Managed Agents (CMA)](https://platform.claude.com/docs/en/managed-agents/overview), allowing you to self-host the sandbox layer while keeping the agent loop entirely within Anthropic's platform. Through this integration, every tool call the agent makes runs inside a Blaxel [sandbox](https://docs.blaxel.ai/Sandboxes/Overview), letting the agent execute commands, work with files and directories, write code, and perform computations in fully isolated environments.
This tutorial walks you through deploying a self-hosted CMA execution environment on Blaxel.
## Prerequisites
* A Python development environment with Docker installed.
* An Anthropic API key. If not, [sign up for an Anthropic account](https://platform.claude.com/) and obtain an API key.
* A Blaxel account and API key. If not, [sign up for a Blaxel account](https://blaxel.ai) and [obtain an API key](/Security/Access-tokens#api-keys).
* The Blaxel CLI. If not, [download and install the Blaxel CLI](../cli-reference/introduction).
* The Anthropic CLI (optional). If not, [download and install the Anthropic CLI](https://platform.claude.com/docs/en/api/sdks/cli).
## How it works
Under this approach, the agent loop is managed by Anthropic, while sandbox creation and tool execution happen on the Blaxel platform.
* When a session starts, Anthropic sends an event to an orchestrator sandbox running on Blaxel. The orchestrator sandbox exposes a public [preview URL](https://docs.blaxel.ai/Sandboxes/Preview-url) that is registered with Anthropic.
* The orchestrator receives events from Anthropic and spawns a worker sandbox for the session. The worker is a short-lived sandbox that exists to execute tool calls, perform file operations and execute code.
* The worker process exits shortly after the session goes idle, and Blaxel auto-deletes the worker sandbox when its TTL (measured from creation) expires. The orchestrator sandbox is a long-running sandbox which continues to exist to receive future events.
## 1. Clone the repository and log in to Blaxel
Clone the [sample repository](https://github.com/blaxel-ai/cma-blaxel-sandbox.git), which includes templates for the orchestrator sandbox, worker sandbox, and related scripts.
```bash theme={null}
git clone https://github.com/blaxel-ai/cma-blaxel-sandbox.git
cd cma-blaxel-sandbox
```
Create the necessary environment variables:
```bash theme={null}
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-dev.txt
export ANTHROPIC_API_KEY=
export BL_WORKSPACE=
export BL_API_KEY=
```
Log in to Blaxel:
```bash theme={null}
bl login
```
## 2. Create a self-hosted environment
Define an [Anthropic environment](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes) for the agent and generate an environment key to authenticate the worker sandbox with Anthropic's work queue.
* On the [Claude Platform](https://platform.claude.com/), navigate to **Managed Agents** > **Environments**.
* Click **Add environment**.
* Configure a new environment:
* Name: "Blaxel"
* Type: Self-hosted
* Once the environment is created, click **Generate environment key**.
* Copy and save the environment key for use in a later step.
Run the following command:
```bash theme={null}
ant beta:environments create \
--name "Blaxel" \
--config '{type: self_hosted}'
```
Note the `id` from the response for use in a later step. Then:
* Navigate to **Managed Agents** > **Environments** on the [Claude Platform](https://platform.claude.com/).
* Select the environment.
* Click **Generate environment key**.
* Copy and save the key for use in a later step.
You can list available environments with `ant beta:environments list --transform "{id,name}" --format jsonl`.
Use one self-hosted environment per Blaxel workspace. Every orchestrator connected to an environment competes for the same work queue, and whichever claims a work item first creates the worker sandbox in its own workspace. If another orchestrator in a different workspace is connected to the same environment, your sessions still complete, but the worker sandboxes appear in that other workspace.
## 3. Build and push the sandbox images
The template repository includes two sandbox images: a worker that runs agent tool calls, and an orchestrator that handles webhooks and spawns workers. The next step is to push these images to Blaxel.
Pushing an image to Blaxel packages your code and uploads it to Blaxel's registry so it can be provisioned as an on-demand sandbox during agent runs. See the [`bl push` reference](https://docs.blaxel.ai/cli-reference/commands/bl_push) for more details.
### Worker image
The worker image is based on `node:22-bookworm-slim` and contains multiple [pre-installed applications and tools](https://platform.claude.com/docs/en/managed-agents/cloud-sandboxes-reference). Push it from the `worker/` directory in the cookbook repo:
```bash theme={null}
( cd worker && bl push --type sandbox )
```
### Orchestrator image
The orchestrator runs a FastAPI server that exposes an endpoint to receive webhook events. Push it from the `orchestrator/` directory:
```bash theme={null}
( cd orchestrator && bl push --type sandbox )
```
Within the orchestrator is a FastAPI webhook server that runs inside a long-lived Blaxel sandbox. It listens for `session.status_run_started` events from Anthropic's CMA platform, verifies the webhook signature, and schedules a background dispatch task for the session. The webhook handler returns 200 immediately; sandbox creation happens in the background.
The main workhorse is `_dispatch_for_session`, which runs as a background task for each incoming session:
```python theme={null}
async def _dispatch_for_session(session_id: str) -> None:
try:
if dispatcher_debounce_ms > 0:
await asyncio.sleep(dispatcher_debounce_ms / 1000)
session_ids = set(_scheduled_session_ids)
session_ids.add(session_id)
session_ids.update(await _queued_work_session_ids())
prepared_workers = await _ready_workers_for_sessions(session_ids)
if session_id not in prepared_workers:
raise RuntimeError(f"worker {_worker_name(session_id)} never became ready")
ok = await _drain_and_dispatch_work(prepared_workers=prepared_workers)
if not ok:
logger.error("background dispatch for session %s had failures", session_id)
except Exception as exc:
logger.error("background dispatch for session %s failed before claim: %r", session_id, exc)
finally:
_scheduled_session_ids.discard(session_id)
```
This function performs the following tasks:
* It debounces briefly so that multiple near-simultaneous webhooks can be batched into a single dispatch cycle, ensuring the queue is efficiently polled.
* It collects all pending session IDs and pre-warms their sandboxes in parallel.
* It calls `_drain_and_dispatch_work`, which claims queued work items via the Anthropic SDK's `work.poller` and, for each item, calls `_dispatch_work_item`.
The `_dispatch_work_item` function is where a specific work item is actually handed off to its session sandbox. It runs `ant beta:worker run` as a process on the pre-readied sandbox, passing `ANTHROPIC_WORK_ID` and `ANTHROPIC_SESSION_ID` as environment variables so the worker binds to that work item:
```python theme={null}
await worker.process.exec({
"name": process_name,
"command": f"ant beta:worker run --workdir /workspace --max-idle {worker_max_idle}",
"wait_for_completion": False,
"keep_alive": True,
"timeout": worker_keepalive_timeout,
"env": process_env,
})
```
The process is started with `wait_for_completion=False` and `keep_alive=True` so the sandbox stays active while `ant run` serves the session. The `ant beta:worker run` command owns the heartbeat and stop logic for the claimed work item; the orchestrator does not heartbeat after handing off.
The `_ensure_worker_ready` function creates Blaxel worker sandboxes (or reuses one if it already exists for that session):
```python theme={null}
async def _ensure_worker_ready(session_id: str):
name = _worker_name(session_id)
spec = {
"name": name,
"image": worker_image,
"memory": 4096,
"ttl": worker_ttl,
}
if worker_region:
spec["region"] = worker_region
spec = await apply_worker_features(spec, session_id=session_id, region=worker_region)
worker = await SandboxInstance.create_if_not_exists(spec)
logger.info("worker %s readying for session %s", name, session_id)
if not await _wait_for_worker_ready(worker, name):
raise RuntimeError(f"worker {name} never became ready")
return worker
```
The sandbox `ttl` field defaults to `2h`, ensuring that Blaxel deletes the sandbox 2 hours after creation regardless of activity. This is a cleanup guarantee for cases where the orchestrator never issues an explicit delete. It should be configured (via `BLAXEL_WORKER_TTL`) to a value greater than the longest expected session length.
To give the agent access to third-party credentials, use [Blaxel Proxy secret injection](/Sandboxes/Proxy-secrets-injection). The proxy injects the credential into the worker's outbound requests, so the secret never enters the worker sandbox or the CMA agent config. The template supports this through the `BLAXEL_WORKER_PROXY_*` variables; see the [cookbook guide](https://github.com/blaxel-ai/cma-blaxel-sandbox/blob/main/GUIDE.md) for the recipe.
## 4. Deploy the orchestrator
Run `setup.py` from the repository to start the orchestrator sandbox and create a public preview URL:
```bash theme={null}
export ANTHROPIC_ENVIRONMENT_ID=
export ANTHROPIC_ENVIRONMENT_KEY=
python3 setup.py
```
`setup.py` prints the orchestrator's public preview URL (e.g. `https://.preview.bl.run/webhook`). Note this URL for the next step.
## 5. Configure the webhook
Register the orchestrator's preview URL in the Claude Platform so the platform notifies it when a new session starts.
* On the [Claude Platform](https://platform.claude.com/), navigate to **Manage** > **Webhooks**.
* Click **Add webhook endpoint**.
* Set the endpoint URL to the preview URL from the previous step and subscribe to the `session.status_run_started` event. This is the only event the orchestrator consumes.
* Once created, copy the generated signing key, then re-run `setup.py` with this value exported:
```bash theme={null}
export ANTHROPIC_WEBHOOK_SIGNING_KEY=
python3 setup.py
```
## 6. Create an agent
Create an agent configuration that defines the model and system prompt for each agent session.
* On the [Claude Platform](https://platform.claude.com/), navigate to **Managed Agents** > **Agents**.
* Click **New agent**.
* Click the **Template** tab and select **Blank agent**.
* In the **Agent config** section, update the following YAML keys:
* `name`: "Blaxel Assistant"
* `model`: "claude-sonnet-4-6"
* `system`: "You are a helpful assistant."
* Make sure the `tools` section includes `- type: agent_toolset_20260401`. This toolset provides the built-in file and bash tools the agent runs in the worker sandbox.
* Click **Create agent**.
```bash theme={null}
ant beta:agents create <<'YAML'
name: Blaxel Assistant
model: claude-sonnet-4-6
system: |
You are a helpful assistant
tools:
- type: agent_toolset_20260401
YAML
```
Note the `id` from the response, as you will need it to start a session.
You can list available agents with `ant beta:agents list --transform "{id,name,model}" --format jsonl`.
## 7. Start a session
With the environment and agent configured, start a session to run the agent.
* On the [Claude Platform](https://platform.claude.com/), navigate to **Managed Agents** > **Sessions**.
* Click **New session**.
* Select your agent and the Blaxel environment, then send an initial message.
Export the agent ID and run the example session from the repository:
```bash theme={null}
export ANTHROPIC_AGENT_ID=
python3 example/run_session.py
```
The script prints the session transcript, ends with `EXAMPLE: PASS`, and verifies the worker sandbox in your workspace before printing the `bl get sandbox` command that inspects it.
Alternatively, start a session using the agent ID and environment ID from the previous steps:
```bash theme={null}
ant beta:sessions create \
--agent \
--environment-id \
--title "Blaxel session"
```
Send an initial message using the session ID from the response:
```bash theme={null}
ant beta:sessions:events send \
--session-id \
--event '{type: user.message, content: [{type: text, text: "Your initial message here"}]}'
```
You can then interactively inspect the session messages:
```bash theme={null}
ant beta:sessions:events list \
--session-id \
--type user.message \
--type agent.message
```
When the session starts, the Claude Platform sends a `session.status_run_started` event to the orchestrator. The orchestrator schedules a background task that creates the worker sandbox, claims the work item from Anthropic's queue, and uses the sandbox to execute tool calls and post results back to Anthropic. The worker process exits when the session goes idle, and the sandbox is auto-deleted when its TTL expires.
The orchestrator sandbox and its public preview URL persist until you remove them. Delete the sandbox when done so the public webhook endpoint does not stay live:
```bash theme={null}
bl delete sandbox cma-orchestrator-app
```
## Resources
Want more info on developing and deploying agents on Blaxel? Check out the following resources:
Operate a remote Blaxel sandbox from your own self-hosted Claude Agent SDK (using MCP).
Deploy Claude Code CLI inside of a Blaxel sandbox.
Complete tutorial for building an agent with Claude Agent SDK and deploying it on Blaxel as a serverless auto-scalable API.
Complete tutorial for managing variables and secrets when deploying to Blaxel.
# Run code-server in a sandbox
Source: https://docs.blaxel.ai/Tutorials/Code-server
Run VS Code in the browser by deploying code-server inside a Blaxel sandbox, exposed securely through preview URLs for remote development.
This tutorial explains how to run code-server inside a Blaxel sandbox and expose it securely using sandbox preview URLs.
## Prerequisites
Before starting, ensure you have:
* [Blaxel CLI](../cli-reference/introduction) installed and authenticated (`bl login`)
## Create a base sandbox image
### Dockerfile
```dockerfile theme={null}
FROM node:23-slim
RUN apt-get update && apt-get install -y \
curl \
wget \
procps \
jq \
sed \
grep \
nano \
vim \
git \
sudo \
python3 \
zip \
tree \
unzip \
ca-certificates \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR /home/user
RUN update-ca-certificates
RUN npm i -g typescript ts-node @types/node dotenv webpack webpack-cli
# Install code-server
RUN curl -fsSL https://code-server.dev/install.sh | sh
# Create /blaxel directory and Next.js app for testing
RUN mkdir -p /blaxel && \
npx create-next-app@latest /blaxel/app --use-npm --typescript --eslint --tailwind --src-dir --app --import-alias "@/*" --no-git --yes --no-turbopack
# Copy sandbox-api
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
# Copy entrypoint script
COPY entrypoint.sh /home/user/entrypoint.sh
RUN chmod +x /home/user/entrypoint.sh
ENTRYPOINT ["/home/user/entrypoint.sh"]
```
### entrypoint.sh
The critical step is configuring code-server to bind on the correct address and trust all origins. This is required because sandbox preview traffic is proxied through Blaxel's infrastructure.
Create an entrypoint script that creates the configuration file and starts the sandbox API and code-server:
```bash theme={null}
#!/bin/bash
# Start sandbox-api in the background
echo "Starting sandbox-api on port 8080..."
/usr/local/bin/sandbox-api &
# Wait for sandbox-api to be ready
while ! curl -s http://127.0.0.1:8080/health > /dev/null 2>&1; do
sleep 0.1
done
echo "Sandbox API ready"
# Write code-server config to bind on port 8081 (CLI args don't override the config file)
mkdir -p /root/.config/code-server
cat > /root/.config/code-server/config.yaml << 'CONF'
bind-addr: [::]:8081
auth: none
cert: false
trusted-origins:
- "*"
CONF
# Start code-server via the sandbox API
echo "Starting code-server on port 8081 via sandbox API..."
curl -s http://127.0.0.1:8080/process -X POST \
-H "Content-Type: application/json" \
-d '{"name":"code-server","command":"code-server --disable-telemetry --config /root/.config/code-server/config.yaml","workingDir":"/home/user","waitForCompletion":false, "env": {"PORT": "8081"}}'
echo "code-server started via sandbox API"
# Keep the entrypoint alive
wait
```
The key configuration settings here are:
* `bind-addr: [::]:8081` - listens on all interfaces over both IPv4 and IPv6 on port 8081 (bracket syntax is required for IPv6 addresses in `host:port` form), the port exposed via the sandbox preview
* `auth: none` - disables password auth (access will be gated using a [private preview URL and token](/Sandboxes/Preview-url), discussed below)
* `cert: false` - disables TLS termination (handled upstream by Blaxel)
* `trusted-origins: ["*"]` - required to allow requests coming from the sandbox proxy origin; without this, code-server will reject WebSocket connections
### blaxel.toml
Create a `blaxel.toml` file in the same directory as your Dockerfile:
```toml theme={null}
type = "sandbox"
name = "code-server-template"
[runtime]
memory = 4096
[[runtime.ports]]
name = "code-server"
target = 8081
protocol = "tcp"
[[runtime.ports]]
name = "debug-sandbox"
target = 8082
protocol = "tcp"
[[runtime.ports]]
name = "nextjs"
target = 3000
protocol = "tcp"
```
## Deploy the sandbox image
Deploy the image by running:
```bash theme={null}
bl deploy
```
## Create or reuse a sandbox
Create a sandbox from the base image:
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-code-server-sandbox";
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
image: "code-server-template:latest",
memory: 4096,
region: "us-was-1",
ports: [
{ name: "preview", target: 8081, protocol: "HTTP" },
],
});
```
## Create the preview URL
code-server runs on port 8081, so we expose that port via a [preview URL](/Sandboxes/Preview-url):
```typescript theme={null}
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "code-server-preview" },
spec: {
public: false,
port: 8081,
},
});
```
## Generate a preview token
To securely access the preview, a token is required:
```typescript theme={null}
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24); // 1 day
const token = await preview.tokens.create(expiresAt);
```
## Start the server
The entrypoint starts code-server automatically. The following function can be used as a fallback to restart it if the process is not running:
```typescript theme={null}
async function startCodeServer(sandbox: SandboxInstance) {
await sandbox.process.exec({
name: "code-server",
command: "code-server --disable-telemetry --config /root/.config/code-server/config.yaml",
workingDir: "/home/user",
waitForPorts: [8081],
restartOnFailure: true,
maxRestarts: 25,
});
}
```
## Access the application
Once everything is running, code-server will be available at `https://?bl_preview_token=`.
## Complete example
Here is a full example combining all the steps:
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-code-server-sandbox";
async function startCodeServer(sandbox: SandboxInstance) {
await sandbox.process.exec({
name: "code-server",
command: "code-server --disable-telemetry --config /root/.config/code-server/config.yaml",
workingDir: "/home/user",
waitForPorts: [8081],
restartOnFailure: true,
maxRestarts: 25,
});
}
async function main() {
try {
// Create or reuse the sandbox
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
image: "code-server-template:latest",
memory: 4096,
region: "us-was-1",
ports: [
{ name: "preview", target: 8081, protocol: "HTTP" },
],
});
// Create preview
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "code-server-preview" },
spec: {
public: false,
port: 8081,
},
});
// Generate preview token
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
const token = await preview.tokens.create(expiresAt);
// Start dev server if not already running
const processes = await sandbox.process.list();
if (!processes.find((p) => p.name === "code-server")) {
await startCodeServer(sandbox);
}
// Print access URL
const webUrl = `${preview.spec?.url}?bl_preview_token=${token.value}`;
console.log(`code-server preview URL: ${webUrl}`);
} catch (error) {
console.error("Error:", error);
}
}
main();
```
# Run Codex in a sandbox
Source: https://docs.blaxel.ai/Tutorials/Codex
Run OpenAI Codex inside a Blaxel sandbox to execute coding tasks on a hosted codebase, with persistent storage and secure network access.
This tutorial explains how to run [OpenAI Codex](https://openai.com/codex) inside a Blaxel sandbox and have it execute coding tasks on a codebase hosted in a different sandbox.
## Prerequisites
Before starting, ensure you have:
* a Python or TypeScript development environment;
* a Blaxel account and API key. If not, [sign up for a Blaxel account](https://blaxel.ai) and [create a Blaxel API key](../Security/Access-tokens#api-keys);
* an OpenAI API key with access to Codex. If not, [create an OpenAI API key](https://platform.openai.com/api-keys).
## Install the Blaxel CLI and SDK
1. [Download and install the Blaxel CLI](https://docs.blaxel.ai/cli-reference/introduction#install) and log in to your Blaxel account:
```shell theme={null}
bl login
```
2. In a new directory, install the Blaxel SDK ([Python](https://github.com/blaxel-ai/sdk-python) and [TypeScript](https://github.com/blaxel-ai/sdk-typescript) are both supported):
```shell TypeScript (npm) theme={null}
npm init # if new project
npm install @blaxel/core
```
```shell TypeScript (pnpm) theme={null}
pnpm init # if new project
pnpm install @blaxel/core
```
```shell TypeScript (yarn) theme={null}
yarn init # if new project
yarn add @blaxel/core
```
```shell TypeScript (bun) theme={null}
bun init -m --yes # if new project
bun install @blaxel/core
```
```shell Python theme={null}
python3 -m venv .venv
source .venv/bin/activate
pip install blaxel
```
## Create sandboxes
1. In the host environment, define the following variables:
```shell theme={null}
export BLAXEL_API_KEY=YOUR-BLAXEL-API-KEY-HERE
export OPENAI_API_KEY=YOUR-OPENAI-API-KEY-HERE
```
2. Create a script named `main.py` (Python) or `index.ts` (TypeScript) in the same directory.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
async function main() {
// Check for required variables
const blaxelApiKey = process.env.BLAXEL_API_KEY;
if (!blaxelApiKey) {
throw new Error("BLAXEL_API_KEY environment variable is not set");
}
const openaiApiKey = process.env.OPENAI_API_KEY;
if (!openaiApiKey) {
throw new Error("OPENAI_API_KEY environment variable is not set");
}
// Create application sandbox
const appSandbox = await SandboxInstance.createIfNotExists({
name: "nextjs-sandbox",
image: "blaxel/nextjs:latest",
memory: 4096,
region: "us-pdx-1",
});
console.log("Application sandbox created");
// Start dev server in application sandbox
await appSandbox.process.exec({
workingDir: "/blaxel/app",
command: "npm run dev -- --hostname $HOST --port 3000"
});
console.log("Application server started");
// Create preview URL
const appPreview = await appSandbox.previews.createIfNotExists({
metadata: { name: "app-preview" },
spec: {
port: 3000,
public: false,
}
});
console.log("Preview URL created");
// Create preview token
// Valid for 24 hours
const expiresAt = new Date(Date.now() + 1440 * 60 * 1000);
const token = await appPreview.tokens.create(expiresAt);
// Get preview URL
console.log(`Preview URL: ${appPreview.spec.url}?bl_preview_token=${token.value}`);
console.log(`MCP URL: ${appSandbox.metadata.url}/mcp`);
// Create Codex sandbox
const codexSandbox = await SandboxInstance.createIfNotExists({
name: "codex-sandbox",
image: "blaxel/codex:latest",
memory: 4096,
region: "us-pdx-1",
envs: [
{ name: "OPENAI_API_KEY", value: openaiApiKey },
{ name: "BLAXEL_API_KEY", value: blaxelApiKey }
]
});
console.log("Codex sandbox created");
}
main();
```
```python Python theme={null}
import asyncio
import os
import sys
from datetime import datetime, timedelta, UTC
from blaxel.core import SandboxInstance
async def main():
# Check for required variables
blaxel_api_key = os.getenv("BLAXEL_API_KEY")
if not blaxel_api_key:
raise ValueError("BLAXEL_API_KEY environment variable is not set")
openai_api_key = os.getenv("OPENAI_API_KEY")
if not openai_api_key:
raise ValueError("OPENAI_API_KEY environment variable is not set")
# Create application sandbox
app_sandbox = await SandboxInstance.create_if_not_exists({
"name": "nextjs-sandbox",
"image": "blaxel/nextjs:latest",
"memory": 4096,
"region": "us-pdx-1",
})
print("Application sandbox created")
# Start dev server in application sandbox
await app_sandbox.process.exec({
"working_dir": "/blaxel/app",
"command": "npm run dev -- --hostname $HOST --port 3000"
})
print("Application server started")
# Create preview URL
app_preview = await app_sandbox.previews.create_if_not_exists({
"metadata": {"name": "app-preview"},
"spec": {
"port": 3000,
"public": False,
}
})
print("Preview URL created")
# Create preview token
# Valid for 24 hours
expires_at = datetime.now(UTC) + timedelta(minutes=1440)
token = await app_preview.tokens.create(expires_at)
# Get preview URL
print(f"Preview URL: {app_preview.spec.url}?bl_preview_token={token.value}")
print(f"MCP URL: {app_sandbox.metadata.url}/mcp")
# Create Codex sandbox
codex_sandbox = await SandboxInstance.create_if_not_exists({
"name": "codex-sandbox",
"image": "blaxel/codex:latest",
"memory": 4096,
"region": "us-pdx-1",
"envs": [
{"name": "OPENAI_API_KEY", "value": openai_api_key},
{"name": "BLAXEL_API_KEY", "value": blaxel_api_key}
]
})
print("Codex sandbox created")
if __name__ == "__main__":
asyncio.run(main())
```
This script creates two Blaxel sandboxes:
* `codex-sandbox` using Blaxel's pre-built Codex image, which includes the Codex CLI
* `nextjs-sandbox` using Blaxel's Next.js base image
In the Codex sandbox, it:
* adds the Blaxel and OpenAI API keys to `codex-sandbox` as environment variables.
In the application sandbox, it:
* starts the Next.js dev server in `nextjs-sandbox` on port 3000;
* creates a preview URL for the Next.js service running in `nextjs-sandbox` on port 3000;
* creates an access token for the preview URL, valid for 24 hours;
* returns the preview URL.
3. Run the script to create the sandboxes and preview URL:
```python Python theme={null}
python main.py
```
```typescript TypeScript theme={null}
bun index.ts
```
Once complete, the script displays the generated preview URL for the Next.js application (for example, `https://b186....preview.bl.run?bl_preview_token=cbba622560db78e...`) and the MCP server URL (for example, `https://sbx-nextjs-sandbox....bl.run/mcp`) for the Next.js sandbox. Note these values, as you will require them in subsequent steps.
## Configure and test Codex
1. Connect to the Codex sandbox terminal:
```shell theme={null}
bl connect sandbox codex-sandbox
```
2. Configure Codex to connect to the application sandbox's MCP server URL (obtained from the sandbox creation script in the previous section) with your Blaxel API key (set in the sandbox environment):
```shell theme={null}
codex mcp add sandbox --url YOUR-SANDBOX-MCP-URL-HERE --bearer-token-env-var BLAXEL_API_KEY
```
3. Start Codex and confirm that it is connected to the sandbox MCP server with the `/mcp` command. This command should return the list of tools available in the sandbox.
## Test Codex
Once Codex starts, give it a coding task referencing the application sandbox, as in the example prompt below:
```shell theme={null}
You have access to a sandbox environment over MCP. The sandbox includes tools to read and write files and directories and run commands. The sandbox includes a skeleton Next.js application at /blaxel/app. Update the application codebase and complete the coding task below.
You must make all your changes only in the sandbox.
Do not make any changes in the local environment.
Your task is: create a website for a new board game of your own invention, including an interactive demo
```
Codex will connect to the application sandbox, inspect the Next.js codebase and make changes as per your request. Once complete, visit the application sandbox's preview URL (obtained from the sandbox creation script in the previous section) to see the result.
## Resources
Want more information on building and deploying with OpenAI on Blaxel? Check out the following resources:
Build compute-capable agents backed by Blaxel sandboxes using OpenAI Agents SDK.
Build and deploy agents with OpenAI Agents SDK to Blaxel.
# Run CrewAI on Blaxel
Source: https://docs.blaxel.ai/Tutorials/CrewAI
Deploy multi-agent CrewAI systems on Blaxel with serverless hosting, agentic observability, and connections to MCP servers and external LLM APIs.
[CrewAI](https://www.crewai.com/) is a framework for orchestrating autonomous AI agents — enabling you to create AI teams where each agent has specific roles, tools, and goals, working together to accomplish complex tasks. You can deploy your CrewAI projects to Blaxel with minimal code editing (and zero configuration), enabling you to use [Serverless Deployments](../Infrastructure/Global-Inference-Network), [Agentic Observability](../Observability/Overview), [Policies](../Model-Governance/Policies), and more.
## Get started with CrewAI on Blaxel
To get started with CrewAI on Blaxel:
* if you already have a CrewAI agent, adapt your code with [Blaxel SDK commands](../Agents/Develop-an-agent) to connect to [MCP servers](../Functions/Overview), [LLMs](../Models/Overview) and [other agents](../Agents/Overview).
* else initialize an example project in CrewAI by using the following Blaxel CLI command and selecting the *CrewAI hello world:*
```bash theme={null}
bl new agent
```
[Deploy](../Agents/Deploy-an-agent) it by running:
```bash theme={null}
bl deploy
```
## Develop a CrewAI agent using Blaxel features
While building your agent in CrewAI, use Blaxel [SDK](../sdk-reference/introduction) to connect to resources already hosted on Blaxel:
* [MCP servers](../Functions/Overview)
* [LLMs](../Models/Overview)
* [other agents](../Agents/Overview)
### Connect to MCP servers
Connect to [MCP servers](../Functions/Overview) using the Blaxel SDK to access pre-built or custom tool servers hosted on Blaxel. This eliminates the need to manage server connections yourself, with credentials stored securely on the platform.
Run the following command to retrieve tools in CrewAI format:
```python Python theme={null}
from blaxel.crewai import bl_tools
await bl_tools(['mcp-server-name'])
```
### Connect to LLMs
Connect to [LLMs](../Models/Overview) hosted on Blaxel using the SDK to avoid managing model API connections yourself. All credentials remain securely stored on the platform.
```python Python theme={null}
from blaxel.crewai import bl_model
model = await bl_model("model-api-name")
```
### Connect to other agents
Connect to other agents hosted on Blaxel from your code by using the [Blaxel SDK](../sdk-reference/introduction). This allows for multi-agent chaining without managing connections yourself. This command is independent of the framework used to build the agent.
```python Python theme={null}
from blaxel.core.agents import bl_agent
response = await bl_agent("agent-name").run(input);
```
### Host your agent on Blaxel
You can [deploy](../Agents/Deploy-an-agent) your agent on Blaxel, enabling you to use [Serverless Deployments](../Infrastructure/Global-Inference-Network), [Agentic Observability](../Observability/Overview), [Policies](../Model-Governance/Policies), and more. This command is independent of the framework used to build the agent.
Either run the following CLI command from the root of your agent repository.
```bash theme={null}
bl deploy
```
Or [connect a GitHub repository to Blaxel](../Agents/Github-integration) for automatic deployments every time you push on *main*.
# Run custom agents on Blaxel
Source: https://docs.blaxel.ai/Tutorials/Custom-Agents
Build and deploy fully custom Python or TypeScript agents on Blaxel without using a specific framework, while keeping your code platform-agnostic.
Blaxel’s development paradigm is designed to have a minimal footprint on your usual development process.
Your custom code remains platform-agnostic: you can deploy it on Blaxel or through traditional methods like Docker containers on VMs or Kubernetes clusters. When you deploy on Blaxel (CLI command `bl deploy`), Blaxel runs a specialized build process that integrates your code with its [Global Agentics Network](../Infrastructure/Global-Inference-Network) features.
At this time, Blaxel only supports custom agents developed in TypeScript or Python.
Here is a high-level overview of how agents can be built and deployed using Blaxel:
1. **Initialize a new project by creating a local git repository**. This will contain your agent's logic and connections, as well as all required dependencies. For quick setup, use [Blaxel CLI](../cli-reference/introduction) command `bl new agent`, which creates a pre-scaffolded local repository ready for development that you can deploy to Blaxel in one command.
2. **Develop and test your agent iteratively in a local environment**.
1. Develop your agent logic however you want (using an agentic framework or any custom TypeScript/Python code). Write your own functions as needed. Use Blaxel SDK commands to connect to resources from Blaxel such as model APIs and tool servers.
2. Use Blaxel CLI command `bl serve` to serve your agent on your local machine. The execution workflow—including agent logic, functions, and model API calls—is broken down and sandboxed exactly as it would be when served on Blaxel.
3. **Deploy your agent**. Use Blaxel CLI command `bl deploy` to build and deploy your agent on Blaxel. You can manage a development & production life-cycle by deploying multiple agents, with the according prefix or label.
## Develop an agent on Blaxel
Develop your AI agents in TypeScript using the Blaxel SDK.
Develop your AI agents in Python using the Blaxel SDK.
# Continue DeepSeek Harness sessions in a Blaxel sandbox
Source: https://docs.blaxel.ai/Tutorials/DeepSeek-Harness
Move a DeepSeek Harness session to Blaxel, continue while your laptop is offline, then review and return the conversation and code changes.
Move your current DeepSeek Harness (DSH) Web session into a private Blaxel sandbox so the agent can keep working while your computer is offline. In this walkthrough, you add invoice status filters, bring the tested changes back to your local repository, and move the same session again.
## Prerequisites
* A [Blaxel account](https://blaxel.ai) and workspace
* Node.js 22.19.0 or newer in Node.js 22, or Node.js 24 or newer
* Git, npm, and pnpm on your computer
* A portable API key for the native DeepSeek or pi-ai model you select in DSH
* A local Git worktree; the steps below create one from the Invoice Desk example
The commands select the current plugin release. The launcher selects its compatible DSH runtime. DSH is in developer preview.
Tested with plugin 0.1.4 and DSH 0.1.2-rc.1.
## 1. Install the plugin and open DSH
Install the plugin in the Web profile:
```bash theme={null}
npx --yes --package @blaxel/dsh-sandbox@latest \
dsh-blaxel plugin --profile web add \
--allow-build=@deepseek-ai/dsh-subprocess-local \
--allow-build=koffi --allow-build=node-pty \
--allow-build=@google/genai --allow-build=protobufjs \
@blaxel/dsh-sandbox@latest
```
Wait for installation to finish, then open the Web interface:
```bash theme={null}
npx --yes --package @blaxel/dsh-sandbox@latest dsh-blaxel web
```
The launcher prepares the compatible runtime, including native conversation import support. Its first run downloads dependencies. Later runs reuse the cache. Use this launcher for subsequent visits, and keep the same `DSH_HOME` if you configure a custom profile.
Read the first-launch notice and continue. If you use a DeepSeek model, configure its key when prompted. To choose a different supported provider, select Configure later and use Settings > Models in step 3.
## 2. Prepare and open Invoice Desk
The [Invoice Desk starter](https://github.com/blaxel-ai/deepseek-harness-blaxel-sandbox/tree/main/examples/invoice-desk) already has CSV export and ten tests. Copy it into a small repository of its own so the handoff contains the application you are changing:
```bash theme={null}
git clone https://github.com/blaxel-ai/deepseek-harness-blaxel-sandbox.git deepseek-blaxel-source
cp -R deepseek-blaxel-source/examples/invoice-desk invoice-desk
cd invoice-desk
git init
npm test
```
The starter tests should pass. In DSH, select Add workspace, choose the `invoice-desk` directory, and open it. Start a conversation in that workspace.
The directory-selection image uses DSH's browser-based folder picker. A desktop installation can show the native operating-system picker instead; choose the same project directory.
## 3. Connect your model and Blaxel
Open Settings > Models and configure the provider you intend to use. Select that model in the conversation's model picker. The captured run uses GPT-4.1 mini through the native OpenAI provider with a portable API key.
Open Settings > Blaxel. Select Sign in to Blaxel and complete browser authorization, then choose your workspace and Verify connection. An existing `bl login` profile also appears here. Use an API key instead exposes the workspace and API-key form when you need that sign-in method.
Under Sandbox defaults, choose 4 GB memory. Expand Advanced settings, select Typescript App, leave Region on Automatic, and choose a maximum lifetime suitable for the task. This run uses 1 hour. Select Save defaults when you change a value.
These defaults affect new sandboxes. Cloud sessions otherwise use a 24-hour maximum lifetime. Choose enough time to return the work before the sandbox expires.
The selected model's portable API key enters the private cloud host so the agent can run independently. Blaxel credentials stay on your computer. Local OAuth grants, laptop-only model endpoints, external skills, and host MCP configuration do not migrate.
The cloud agent has full access inside its dedicated VM without per-tool approval. Use a model and project tools you trust with the model API key. Keep credentials out of the project files.
## 4. Start the task locally
Send this prompt in the local conversation:
```text wrap theme={null}
Read README.md, server.mjs and invoices.mjs. I want an invoice status filter with All, Paid and Pending options, keeping Export CSV. Give me a short implementation plan. Do not edit files yet. Do not read credentials or environment variables. Do not delegate.
```
After the plan, paste the implementation prompt below into the composer. Leave it unsent so you can see the draft travel with the conversation:
```text wrap theme={null}
Implement that invoice status filter now. Add All, Paid and Pending links to the billing page using a status query parameter, and have the CSV export respect the selected status. Keep the existing layout and amount formatting. Add focused node:test checks for filtering without mutating the input and for unknown status values. Run npm test and verify the HTTP page and CSV routes using Node fetch against a temporary server. Stop that server afterward. Give a concise summary of the changed files and actual checks. Do not read credentials or environment variables. Do not delegate or ask follow-up questions.
```
## 5. Move the session to Blaxel
Select Move to Blaxel. The launch panel reports workspace preparation, sandbox startup, and conversation restoration. Wait for the private cloud page to open and show Your session is on Blaxel.
The plugin copies tracked files and unignored untracked files, including current edits. It restores the native conversation, referenced images, and pending draft. A running root task can move at a safe tool boundary; wait for local child agents to finish before moving.
Git-ignored files, `.git`, `.dsh-blaxel`, common credential files, and private keys are excluded from the snapshot. Install any project dependencies required inside the sandbox. Invoice Desk needs no extra packages.
Confirm the implementation prompt is still in the cloud composer, then send it. Model requests, tools, and new conversation history now run in the sandbox. Once the cloud page is ready, closing the original DSH process does not stop that cloud agent.
The local conversation is read-only while Blaxel owns execution:
Copy private link provides access to the cloud session from another browser. Treat the link as a credential. Links expire after 24 hours; reopening through Open cloud session on the original computer renews access. Link renewal does not extend the sandbox's lifetime.
If access fails, reopen the session through the original DSH launcher and use its fresh private link. If Chrome still blocks it, investigate the browser restriction; do not make the sandbox public to bypass the error.
## 6. Verify the cloud work
Read the agent's actual test output before returning. The example started with ten tests; the captured run finishes with thirteen passing tests, including the new filter and HTTP checks. An agent can stop with an incomplete result, so verify its output rather than assuming the first response means success.
In this run, the first response imported an unavailable fetch library. Start by asking the agent to use Node's built-in fetch:
```text wrap theme={null}
Finish the requested verification now. Use Node's built-in global fetch, with no fetch-library imports and no new dependencies. Read the test files first, fix any failed test or route, run npm test until it passes, and stop any temporary server. Do not inspect credentials or environment variables. Do not delegate or ask follow-up questions. Report only the actual passing test count and changed files.
```
The HTTP tests also needed an explicit loopback host and port for their temporary server, instead of inheriting the cloud host's port. Status assertions needed to check table rows because the navigation contains every status label. We corrected those issues and verified all thirteen tests independently.
Review the changed source as well. If a text file unexpectedly appears as a binary patch, cancel the return and have the agent inspect and correct it before proceeding.
## 7. Review and bring the session back
On your original computer, start DSH with the same profile and launcher. Select Move back to local in the cloud page or the local Blaxel settings. Review the changed paths and actual patch, then select Apply changes and move back.
The example review above comes from the second handoff's README update. Your first return lists the application and test changes. The review always reflects the current sandbox's changes.
The return checks the patch against the current local worktree before writing. It checkpoints the cloud root, waits for active child agents, imports the conversation and supported child histories, restores the pending draft and original local permissions, then stops the sandbox after the files and history are durable locally.
In a terminal inside `invoice-desk`, run:
```bash theme={null}
npm test
npm start
```
Open `http://localhost:3000`. Select Pending and confirm only the pending invoice appears. Select Paid and All to check the other views. Export CSV follows the chosen filter.
You can inspect the filtered CSV response independently in another terminal:
```bash theme={null}
curl --fail 'http://localhost:3000/export.csv?status=Pending'
```
Expect a header and one pending invoice. The captured checks also verified three rows for All and two for Paid. If Chrome blocks an attachment download, keep browser download verification separate from a passing HTTP check. Stop the example server with Ctrl+C when finished.
## 8. Move the updated session again
Add a short local checkpoint to `README.md` and save it. For example:
```markdown theme={null}
## Local checkpoint
The status filter and filtered CSV export are working locally.
```
In the same returned conversation, paste this prompt and leave it unsent:
```text wrap theme={null}
Read README.md and add a short Status filters section explaining All, Paid and Pending and that Export CSV follows the selection. Preserve the existing Local checkpoint section. Run npm test. Do not change application code, inspect credentials or environment variables, or delegate. Summarize in one sentence.
```
Select Move to Blaxel again. The new sandbox starts with the earlier cloud changes, your new local checkpoint, and the same conversation. Send the prompt from the cloud page. After the tests pass, review and return the README change. Check that both the Status filters section and Local checkpoint remain locally.
Reopening an existing sandbox reconnects to its current files. It does not upload newer local edits. Files transfer when you move and return the session; this is not continuous synchronization.
## 9. Recover safely and finish
If local edits conflict with the return patch, the plugin keeps both copies, leaves the sandbox available, and identifies a recovery patch under the repository's private Git directory. Preserve your local edits, resolve the conflicting file changes, and retry Move back to local.
Restarting the original DSH host does not require a new sandbox. Reopen the same profile and session, then use Open cloud session or retry the return. If an import was interrupted, retry from that original profile so the durable transfer receipts can resume completed work.
After a successful return, Settings > Blaxel no longer lists an active sandbox for that session:
Discard permanently removes unreturned cloud work. Sandbox expiry also makes remote-only work unavailable. Return the changes you need before discarding the sandbox or reaching its maximum lifetime.
For transfer limits, supported session data, and additional recovery cases, read the [cloud handoff contract](https://github.com/blaxel-ai/deepseek-harness-blaxel-sandbox/blob/main/docs/cloud-session-handoff.md).
## Resources
Start with the billing application used throughout this walkthrough.
Review installation, compatibility, and release information.
Learn how Blaxel sandboxes provide isolated compute for your agents.
Configure sandbox lifetime and automatic deletion policies.
# Run Docker in a sandbox
Source: https://docs.blaxel.ai/Tutorials/Docker
Build, push, and run Docker images inside a Blaxel sandbox, and expose the running containers securely to the internet through preview URLs.
This tutorial explains how to build and push Docker images inside a Blaxel sandbox. It also explains how to run Docker containers and expose them securely using sandbox preview URLs.
## Prerequisites
* [Blaxel CLI](../cli-reference/introduction) installed and authenticated (`bl login`)
* Node.js 18+ or Python 3.12+ installed
* Blaxel SDK installed in your project (`npm install @blaxel/core` or `pip install blaxel`)
## Create and connect to a sandbox
Blaxel provides a ready-to-use sandbox image (`blaxel/docker-in-sandbox`) with everything you need to build and run Docker images. This image also includes the Docker CLI, Docker Compose and `iptables-legacy` (as a replacement for `nftables`).
Create and deploy a sandbox using the Docker sandbox image:
```shell Blaxel CLI theme={null}
bl apply -f - <
Connect to the sandbox terminal for subsequent commands:
```shell theme={null}
bl connect sandbox my-docker-sandbox
```
## Run a Docker container
You can run a container from any public image. Here is an example of running an NGINX container:
```shell theme={null}
docker run -d -p 8081:80 --name my-nginx nginx
```
Check that it is running:
```shell theme={null}
docker ps
```
## Open a shell into a running Docker container
Use `docker exec` to open a shell inside the running container:
```shell theme={null}
docker exec -it my-nginx /bin/sh
```
## Expose a Docker container at a sandbox preview URL
Once a Docker container is running inside your sandbox, you can expose it externally via a [preview URL](/Sandboxes/Preview-url).
To see this in action, run an NGINX container on sandbox port 8081:
```shell theme={null}
docker run -d -p 8081:80 nginx
```
Then, use the Blaxel SDK to create a preview URL for that port:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-docker-sandbox");
const preview = await sandbox.previews.create({
metadata: { name: "nginx-preview" },
spec: {
port: 8081,
public: true
}
});
console.log("Preview URL:", preview.spec?.url);
```
```python Python theme={null}
import asyncio
from blaxel.core import SandboxInstance
async def main():
sandbox = await SandboxInstance.get("my-docker-sandbox")
preview = await sandbox.previews.create({
"metadata": {"name": "nginx-preview"},
"spec": {
"port": 8081,
"public": True
}
})
print("Preview URL:", preview.spec.url)
if __name__ == "__main__":
asyncio.run(main())
```
Now, when you access the preview URL, you should see the default NGINX welcome page.
To restrict access, you can create a [private preview URL](/Sandboxes/Preview-url/#private-preview-urls) instead.
## Build and push a Docker image
You can also build and push images from the sandbox.
To see this in action, first create a minimal `Dockerfile` in the sandbox's `/app` directory:
```Dockerfile theme={null}
FROM alpine:latest
CMD ["echo", "Hello from Blaxel"]
```
Then, build and push it to [ttl.sh](https://ttl.sh), a transient image registry:
If you plan to run the final image on a Blaxel sandbox, add the `--platform linux/amd64` flag when building so the image runs correctly.
```shell Blaxel CLI theme={null}
cd /app
docker build --platform linux/amd64 -t ttl.sh/my-app:1h .
docker push ttl.sh/my-app:1h
```
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-docker-sandbox");
const image = "ttl.sh/my-app:1h";
const build = await sandbox.process.exec({
command: `docker build --platform linux/amd64 -t ${image} /app`,
waitForCompletion: true,
});
console.log(build.logs);
const push = await sandbox.process.exec({
command: `docker push ${image}`,
waitForCompletion: true,
});
console.log(push.logs);
```
```python Python theme={null}
import asyncio
from blaxel.core import SandboxInstance
async def main():
sandbox = await SandboxInstance.get("my-docker-sandbox")
image = "ttl.sh/my-app:1h"
build = await sandbox.process.exec({
"command": f"docker build --platform linux/amd64 -t {image} /app",
"wait_for_completion": True,
})
print(build.logs)
push = await sandbox.process.exec({
"command": f"docker push {image}",
"wait_for_completion": True,
})
print(push.logs)
if __name__ == "__main__":
asyncio.run(main())
```
Once the image is published to the registry, you can test it with `docker run`:
```shell theme={null}
docker run ttl.sh/my-app:1h
```
## Use Docker Compose
The Docker sandbox image includes a sample Docker Compose application in the `example` directory. Try it with the following commands:
```shell theme={null}
cd example
docker compose up
```
Ensure that your sandbox is configured with sufficient memory for the build process and output.
# Run Expo in a sandbox
Source: https://docs.blaxel.ai/Tutorials/Expo
Run an Expo (React Native / Web) application inside a Blaxel sandbox and expose it securely through preview URLs with step-by-step setup.
This tutorial explains how to run an Expo (React Native / Web) application inside a Blaxel sandbox and expose it securely using sandbox preview URLs.
## Prerequisites
* [Blaxel CLI](../cli-reference/introduction) installed and authenticated (`bl login`)
* Node.js 18+ installed
* `@blaxel/core` package installed in your project (`npm install @blaxel/core`)
## Architecture overview
Running Expo inside a Blaxel sandbox requires a few adjustments:
1. Injecting the preview URL into `app.json`
2. Setting `EXPO_PACKAGER_PROXY_URL` to force Expo to proxy assets through the preview URL
3. Restarting the dev server after configuration changes
4. Exposing the Expo dev server via a Blaxel preview URL
## Create a base sandbox image
### Dockerfile
```dockerfile theme={null}
FROM node:22-alpine
RUN apk update && apk add --no-cache \
git \
curl \
netcat-openbsd \
&& rm -rf /var/cache/apk/*
WORKDIR /app
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
# Create Expo project with default template
RUN npx create-expo-app@latest .
# Install web dependencies and expo-asset for asset loading support
RUN npx expo install react-dom react-native-web expo-asset
# Patch expo-asset: it hardcodes http:// when constructing asset URLs
# from the manifest's debuggerHost. Behind the Blaxel HTTPS proxy,
# Android's native downloader can't follow the HTTP→HTTPS redirect,
# so fonts and images fail to load. Force https://.
RUN sed -i "s|'http://' + manifest2.extra.expoGo.debuggerHost|'https://' + manifest2.extra.expoGo.debuggerHost|g" \
node_modules/expo-asset/build/AssetSources.js
# Pre-warm Metro bundler cache by starting the dev server briefly
# This warms up the cache better than export since it matches actual dev usage
RUN timeout 120 npx expo start --web --port 8081 --no-dev-client 2>/dev/null || true
# Expose port for Expo web
EXPOSE 8081
# Add npm global modules to PATH
ENV PATH="/usr/local/bin:$PATH"
ENTRYPOINT ["/usr/local/bin/sandbox-api"]
```
### blaxel.toml
Create a `blaxel.toml` file in the same directory as your Dockerfile:
```toml theme={null}
type = "sandbox"
name = "expo-template"
[runtime]
memory = 8096
[[runtime.ports]]
name = "expo-web"
target = 8081
```
## Deploy the sandbox image
Deploy the image by running:
```bash theme={null}
bl deploy
```
## Create or reuse a sandbox
Create a sandbox from the base image:
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-expo-sandbox";
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "expo",
},
image: "expo-template:latest",
memory: 8096,
});
```
## Configure CORS for preview URL access
Expo servers require permissive CORS headers when accessed through a preview URL:
```typescript theme={null}
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
```
Alternatively, you can use [custom domains](https://docs.blaxel.ai/Infrastructure/Custom-domains) to expose previews on your own domain.
## Create the preview URL
Expo runs on port 8081, so expose that port via a [preview URL](../Sandboxes/Preview-url):
```typescript theme={null}
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "dev-server-preview" },
spec: {
responseHeaders,
// Must be public for Android scenarios: Expo Go on Android doesn't forward the auth
// cookie to subsequent bundle/asset requests, so private previews
// fail with 401 when fetching the JS bundle.
// WARNING: This makes the preview publicly accessible with no authentication.
// Do not use this pattern for sensitive workloads.
// If Android support is not required, set to false for private previews
public: true,
port: 8081,
},
});
```
## Generate a preview token
To securely access the preview, a token is required:
```typescript theme={null}
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24); // 1 day
const token = await preview.tokens.create(expiresAt);
```
## Inject the preview URL into Expo's app.json
The Expo application router requires the correct origin when running behind a proxy:
```typescript theme={null}
async function addRouterOriginToAppJson(
sandbox: SandboxInstance,
previewUrl: string
) {
const appJsonPath = "/app/app.json";
const appJsonContent = await sandbox.fs.read(appJsonPath);
const appJson = JSON.parse(appJsonContent);
appJson.expo = {
...appJson.expo,
extra: {
...(appJson.expo.extra || {}),
router: {
...(appJson.expo.extra?.router || {}),
origin: previewUrl,
},
},
};
await sandbox.fs.write(appJsonPath, JSON.stringify(appJson, null, 2));
}
```
## Configure the proxy URL
Expo must be configured to serve assets through the preview URL. This function checks if the configuration is already correct and only updates if needed:
```typescript theme={null}
async function configureExpoProxyUrl(
sandbox: SandboxInstance,
previewUrl: string
): Promise {
const baseUrl = previewUrl.replace(/\/$/, ""); // Remove trailing slash
// Check if the .env file already has the correct proxy URL
let envContent = "";
try {
envContent = await sandbox.fs.read("/app/.env");
} catch {
// File doesn't exist yet
}
const expectedEnvLine = `EXPO_PACKAGER_PROXY_URL=${baseUrl}`;
if (envContent.includes(expectedEnvLine)) {
console.log("Expo proxy URL already configured correctly");
return false; // No changes made
}
// Remove any existing EXPO_PACKAGER_PROXY_URL line and add the new one
const lines = envContent
.split("\n")
.filter((line) => !line.startsWith("EXPO_PACKAGER_PROXY_URL="));
lines.push(expectedEnvLine);
await sandbox.fs.write("/app/.env", lines.join("\n"));
console.log(`Configured Expo to use proxy URL: ${baseUrl}`);
return true; // Changes were made
}
```
## Start the dev server
After setting the proxy URL you can start the dev server:
```typescript theme={null}
async function startDevServer(sandbox: SandboxInstance) {
await sandbox.process.exec({
name: 'dev-server',
command: 'npx expo start --web --port 8081',
workingDir: '/app',
waitForPorts: [8081],
restartOnFailure: true,
maxRestarts: 25,
})
}
```
## Stream logs
Monitor the Expo server output in real-time:
```typescript theme={null}
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log);
},
});
// When done monitoring, close the stream:
logStream.close();
```
## Access the Expo application
Once everything is running, the Expo application will be available at `https://?bl_preview_token=`.
## Complete example
Here is a full example combining all the steps:
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-expo-sandbox";
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
async function addRouterOriginToAppJson(
sandbox: SandboxInstance,
previewUrl: string
) {
const appJsonPath = "/app/app.json";
const appJsonContent = await sandbox.fs.read(appJsonPath);
const appJson = JSON.parse(appJsonContent);
appJson.expo = {
...appJson.expo,
extra: {
...(appJson.expo.extra || {}),
router: {
...(appJson.expo.extra?.router || {}),
origin: previewUrl,
},
},
};
await sandbox.fs.write(appJsonPath, JSON.stringify(appJson, null, 2));
}
async function configureExpoProxyUrl(
sandbox: SandboxInstance,
previewUrl: string
): Promise {
const baseUrl = previewUrl.replace(/\/$/, "");
let envContent = "";
try {
envContent = await sandbox.fs.read("/app/.env");
} catch {
// File doesn't exist
}
const expectedEnvLine = `EXPO_PACKAGER_PROXY_URL=${baseUrl}`;
if (envContent.includes(expectedEnvLine)) {
return false;
}
const lines = envContent
.split("\n")
.filter((line) => !line.startsWith("EXPO_PACKAGER_PROXY_URL="));
lines.push(expectedEnvLine);
await sandbox.fs.write("/app/.env", lines.join("\n"));
return true;
}
async function startDevServer(sandbox: SandboxInstance) {
await sandbox.process.exec({
name: 'dev-server',
command: 'npx expo start --web --port 8081',
workingDir: '/app',
waitForPorts: [8081],
restartOnFailure: true,
maxRestarts: 25,
})
}
async function restartDevServer(sandbox: SandboxInstance) {
try {
await sandbox.process.kill('dev-server')
} catch {
// Process might not be running
}
await new Promise((r) => setTimeout(r, 1000))
await startDevServer(sandbox)
}
async function configureExpo(sandbox: SandboxInstance, previewUrl: string) {
await addRouterOriginToAppJson(sandbox, previewUrl)
const proxyChanged = await configureExpoProxyUrl(sandbox, previewUrl)
const processes = await sandbox.process.list()
const devServerRunning = processes.find((p) => p.name === 'dev-server')
if (proxyChanged && devServerRunning) {
await restartDevServer(sandbox)
} else if (!devServerRunning) {
await startDevServer(sandbox)
}
}
async function main() {
try {
// Create or reuse the sandbox
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "expo",
},
image: "expo-template:latest",
memory: 8096,
});
// Create preview
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "preview" },
spec: {
responseHeaders,
// Must be public for Android scenarios: Expo Go on Android doesn't forward the auth
// cookie to subsequent bundle/asset requests, so private previews
// fail with 401 when fetching the JS bundle.
// WARNING: This makes the preview publicly accessible with no authentication.
// Do not use this pattern for sensitive workloads.
// If Android support is not required, set to false for private previews
public: true,
port: 8081,
},
});
// Generate preview token
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
const token = await preview.tokens.create(expiresAt);
// Configure Expo (will restart dev server if needed)
await configureExpo(sandbox, preview.spec?.url!);
// Print access URLs
const webUrl = `${preview.spec?.url}?bl_preview_token=${token.value}`;
// Use exps:// (HTTPS variant) so Expo Go connects over TLS.
// Android 9+ blocks cleartext HTTP, and the Blaxel proxy redirects
// HTTP→HTTPS which Android's native downloader can't follow.
const expoUrl = webUrl.replace("https://", "exps://");
console.log(`Web Preview URL: ${webUrl}`);
console.log(`Expo Mobile URL: ${expoUrl}`);
// Stream logs
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log)
},
});
// Keep running until interrupted
process.on("SIGINT", () => {
logStream.close();
process.exit(0);
});
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();
```
# Run coding agents in Blaxel sandboxes from Herdr
Source: https://docs.blaxel.ai/Tutorials/Herdr
Use the Herdr Blaxel plugin to run a coding agent in a persistent sandbox, reconnect, preview an application, and apply reviewed changes locally.
Use [Herdr](https://herdr.dev) to run a coding agent in a persistent Blaxel sandbox while your Git worktree stays on your computer. In this tutorial, you update an invoice summary, reconnect to the agent, open a private preview, and bring the changes back to your local files.
## Prerequisites
* A [Blaxel account](https://app.blaxel.ai) and workspace with permission to create sandboxes
* [Herdr](https://herdr.dev/docs/) `0.8.0` or newer on macOS or Linux
* [Node.js](https://nodejs.org/en/download) `22` or newer, with npm and Git available locally
* The [Blaxel CLI](/cli-reference/introduction#install) installed locally; this walkthrough is verified with `0.1.110`
* Access to Codex through an account or an `OPENAI_API_KEY` for the agent running inside the sandbox
This walkthrough uses Codex. The plugin also supports Claude Code, OpenCode, and Pi. It installs the selected coding tool inside the sandbox; you do not need the tool installed locally or credentials in the invoice application.
## 1. Install the plugin and connect to Blaxel
Log in to Blaxel and check your active workspace:
```bash theme={null}
bl login
bl workspaces --current
```
Install the plugin:
```bash theme={null}
herdr plugin install blaxel-ai/herdr-blaxel-sandbox-plugin
herdr plugin list
herdr plugin action list --plugin blaxel.sandbox
```
Confirm that Blaxel Sandbox appears, with actions for Start, reconnect, applying changes, previews, and the dashboard.
## 2. Configure the agent and previews
Open the dashboard and press `t` to choose Codex, Claude Code, OpenCode, or Pi. Press `w` to choose the workspace for new sandboxes. Existing sandboxes keep their original tool and workspace.
```bash theme={null}
herdr plugin action invoke dashboard --plugin blaxel.sandbox
```
For this Codex walkthrough, find the plugin's configuration directory:
```bash theme={null}
herdr plugin config-dir blaxel.sandbox
```
Create or edit `config.json` in that directory. Keep any existing settings you need and set:
```json theme={null}
{
"agent": "codex",
"previewPorts": [3000],
"publicPreviews": false
}
```
The plugin uses your current Blaxel CLI workspace, a `blaxel/ts-app:latest` sandbox with `4096` MB of memory, and a seven-day idle deletion policy by default. Set `workspace` to a workspace name if you need a fixed target for new sandboxes. Existing mappings keep the workspace they were created in.
For agent authentication, complete the Codex login inside the sandbox when prompted. If `OPENAI_API_KEY` is already available to the Herdr process, the plugin passes it as an encrypted sandbox secret and signs Codex in with it. It does not copy local coding-agent sessions or credential files.
Keep credentials out of `config.json` and `agentArgs`. The [configuration reference](https://github.com/blaxel-ai/herdr-blaxel-sandbox-plugin/blob/main/docs/configuration.md) also covers Claude Code, OpenCode, Pi, regions, upload limits, and expiration settings.
## 3. Prepare the invoice project
Clone the plugin repository to get its runnable example, then copy only the example into a fresh Git worktree:
```bash theme={null}
git clone https://github.com/blaxel-ai/herdr-blaxel-sandbox-plugin.git
example_dir="$(mktemp -d "${TMPDIR:-/tmp}/herdr-invoice-summary.XXXXXX")"
cp -R herdr-blaxel-sandbox-plugin/examples/invoice-summary/. "$example_dir/"
cd "$example_dir"
git init
git add .
git -c user.name="Herdr Example" -c user.email="example@localhost" commit -m "Invoice summary baseline"
npm test
npm start
```
The example has no dependencies to install. Its three tests pass, and the initial summary is:
```json theme={null}
{
"count": 3,
"totalCents": 439550,
"paidCents": 125000,
"openCents": 314550
}
```
Open the directory printed by `pwd` as a workspace in Herdr. Use a local shell pane in that workspace for the plugin commands below.
## 4. Start an agent in Blaxel
Run Start from the invoice project's local Herdr pane:
```bash theme={null}
herdr plugin action invoke start-agent --plugin blaxel.sandbox
```
Start displays the target and filtered upload manifest, creates a sandbox, uploads the eligible files, installs the configured agent, and opens a Blaxel agent pane. The plugin excludes Git-ignored files, `.git`, dependencies, environment files, common credentials, and recognized secrets from the upload.
Each Start creates an independent sandbox, including repeated Starts from the same pane. To return to an existing sandbox, use Connect in the dashboard.
Open the dashboard from a local Herdr shell:
```bash theme={null}
herdr plugin action invoke dashboard --plugin blaxel.sandbox
```
Select the invoice project's sandbox. Check its workspace, agent, and state. Press `Enter` or `c` to connect to its agent pane.
## 5. Update and test the invoice summary
Give the remote agent this task:
```text theme={null}
Add an overdueCents field to summarize(rows, asOf), using an explicit YYYY-MM-DD cutoff date. An invoice is overdue only when it is open and its due date is earlier than asOf. For 2026-09-08, the sample ledger should report 84550 overdue cents. Keep the existing totals unchanged. Update the CLI to use that cutoff date. Add tests for paid invoices, invoices due on the cutoff date, and an empty ledger. Run npm test and npm start, then show git diff.
```
Check the command output from the agent. The summary should include `overdueCents: 84550`, and all tests should pass. The edits remain in the sandbox until you approve applying them locally.
## 6. Reconnect to the same session
Close the Blaxel agent pane. Open the dashboard again from a local Herdr shell:
```bash theme={null}
herdr plugin action invoke dashboard --plugin blaxel.sandbox
```
Select the same sandbox and press `Enter` or `c`. The plugin reattaches to its persistent remote `tmux` session. Ask the agent to run `npm test` again and confirm that its changes are still present.
The dashboard selects the exact sandbox even when the project has several agents. Local `herdr` commands run in your local shell pane; the agent pane runs inside the sandbox.
## 7. Open a private application preview
The invoice example starts as a command-line program. To view its result through a browser, ask the remote agent to create `server.mjs` with this content:
```javascript theme={null}
import { createServer } from 'node:http';
import { invoices, summarize } from './invoices.mjs';
createServer((request, response) => {
if (request.method !== 'GET' || request.url !== '/') {
response.writeHead(404).end('Not found');
return;
}
response.writeHead(200, {
'Content-Type': 'application/json; charset=utf-8',
'Cache-Control': 'no-store',
});
response.end(JSON.stringify(summarize(invoices, '2026-09-08'), null, 2));
}).listen(3000, '0.0.0.0');
```
Ask the agent to start `node server.mjs`, keep it running, and verify `http://127.0.0.1:3000/` from inside the sandbox. The server must listen on `0.0.0.0` for Blaxel to route preview traffic to it.
If Codex requests permission to bind the port or make a local HTTP request, review and approve that command in the remote agent pane.
In the local Herdr dashboard, select the same sandbox and press `p` for Previews. Open the temporary URL shown for port `3000`. Your browser should display the JSON summary, including the overdue balance.
Keep `publicPreviews` set to `false`. The plugin generates a private preview token valid for ten minutes; reopen Previews to obtain a new link when it expires. Treat that temporary URL as a credential.
## 8. Review and apply the changes locally
Ask the agent to show `git diff` and finish any file edits before applying. In the local dashboard, select the sandbox and press `a` for Apply.
The plugin exports the remote changes against the upload baseline, checks the patch against your current local files, and shows the complete patch with colored additions and deletions. Use Page Up and Page Down to review it, then answer `y` to apply or press Escape to cancel. Exports larger than 1 MiB are refused before download or local changes; reduce the remote changes before retrying.
Back in the local invoice project, verify the result:
```bash theme={null}
git diff
npm test
npm start
```
Confirm that `overdueCents` is `84550`, the tests pass, and `server.mjs` is present. If a local change conflicts with the remote patch, the plugin applies nothing. Resolve or stash the conflicting local edits, then retry Apply.
## 9. Delete the sandbox
After verifying the local changes, select the example's sandbox in the dashboard and press `d` for Delete. Check the sandbox name in the confirmation and type `DELETE` to permanently remove it.
Refresh the dashboard and confirm that the mapping is gone. If you started more than one sandbox for the exercise, delete each one you no longer need.
Stop ends the agent session and preserves sandbox files. Delete permanently removes the sandbox and its remote files. Apply and verify any changes you want to keep before deleting it.
## Troubleshooting
| Symptom | Action |
| - | - |
| Blaxel Sandbox is missing from Herdr | Check `herdr --version`, reinstall the plugin, and run `herdr plugin list` |
| Start cannot resolve a Blaxel workspace | Run `bl login` and `bl workspaces --current` in your local shell; check any configured `workspace` value |
| Start reports that the snapshot changed | Return to the intended local project pane and rerun Start after file edits have stopped |
| The agent asks for authentication | Complete login inside the sandbox; local coding-agent credentials are not copied |
| The terminal fails with `websocket: bad handshake` | With CLI `0.1.110`, run `bl login ` for the workspace selected in the plugin, then reconnect; this updates the CLI current workspace |
| Reconnect reports a missing sandbox | Check whether it expired or was deleted; Start creates a new sandbox from your current local files |
| Port 3000 has no preview or the link expired | Keep the server running on `0.0.0.0:3000`, check `previewPorts`, and reopen Previews for a fresh link |
| Apply reports a conflict | Resolve or stash conflicting local edits, then retry; do not force-apply the exported patch |
## Resources
Read the plugin reference and run the invoice summary example.
Learn how preview URLs and access tokens connect your browser to a sandbox application.
Configure lifetime and idle deletion policies for your sandboxes.
# Run LangChain on Blaxel
Source: https://docs.blaxel.ai/Tutorials/LangChain
Deploy LangChain and LangGraph projects to Blaxel with minimal code changes to get serverless hosting, agentic observability, and policies.
[LangChain](https://www.langchain.com/) is a composable framework to build LLM applications. It can be combined with [LangGraph](https://www.langchain.com/langgraph) which is a stateful orchestration framework that brings added control to agent workflows. You can deploy your LangChain or LangGraph projects to Blaxel with minimal code editing (and zero configuration), enabling you to use [Serverless Deployments](../Infrastructure/Global-Inference-Network), [Agentic Observability](../Observability/Overview), [Policies](../Model-Governance/Policies), and more.
## Get started with LangChain on Blaxel
To get started with LangChain/LangGraph on Blaxel:
* if you already have a LangChain or LangGraph agent, adapt your code with [Blaxel SDK commands](../Agents/Develop-an-agent) to connect to [MCP servers](../Functions/Overview), [LLMs](../Models/Overview) and [other agents](../Agents/Overview).
* or initialize an example project in LangChain by using the following Blaxel CLI command and selecting the *LangChain hello world:*
```bash theme={null}
bl new agent
```
[Deploy](../Agents/Deploy-an-agent) it by running:
```bash theme={null}
bl deploy
```
## Develop a LangChain agent using Blaxel features
While building your agent in LangChain, use Blaxel [SDK](../sdk-reference/introduction) to connect to resources already hosted on Blaxel:
* [MCP servers](../Functions/Overview)
* [LLMs](../Models/Overview)
* [other agents](../Agents/Overview)
### Connect to MCP servers
Connect to [MCP servers](../Functions/Overview) using the Blaxel SDK to access pre-built or custom tool servers hosted on Blaxel. This eliminates the need to manage server connections yourself, with credentials stored securely on the platform.
Run the following command to retrieve tools in LangChain format:
```python Python theme={null}
from blaxel.langgraph import bl_tools
await bl_tools(['mcp-server-name'])
```
```typescript TypeScript theme={null}
import { blTools } from '@blaxel/langgraph';
const tools = await blTools(['mcp-server-name'])
```
### Connect to LLMs
Connect to [LLMs](../Models/Overview) hosted on Blaxel using the SDK to avoid managing model API connections yourself. All credentials remain securely stored on the platform.
```python Python theme={null}
from blaxel.langgraph import bl_model
model = await bl_model("model-api-name")
```
```typescript TypeScript theme={null}
import { blModel } from "@blaxel/langgraph";
const model = await blModel("model-api-name");
```
### Connect to other agents
Connect to other agents hosted on Blaxel from your code by using the [Blaxel SDK](../sdk-reference/introduction). This allows for multi-agent chaining without managing connections yourself. This command is independent of the framework used to build the agent.
```python Python theme={null}
from blaxel.core.agents import bl_agent
response = await bl_agent("agent-name").run(input);
```
```typescript TypeScript theme={null}
import { blAgent } from "@blaxel/core";
const myAgentResponse = await blAgent("agent-name").run(input);
```
### Host your agent on Blaxel
You can [deploy](../Agents/Deploy-an-agent) your agent on Blaxel, enabling you to use [Serverless Deployments](../Infrastructure/Global-Inference-Network), [Agentic Observability](../Observability/Overview), [Policies](../Model-Governance/Policies), and more. This command is independent of the framework used to build the agent.
Either run the following CLI command from the root of your agent repository.
```bash theme={null}
bl deploy
```
Or [connect a GitHub repository to Blaxel](../Agents/Github-integration) for automatic deployments every time you push on *main*.
# Run LlamaIndex on Blaxel
Source: https://docs.blaxel.ai/Tutorials/LlamaIndex
Deploy LlamaIndex agent and RAG projects to Blaxel with minimal code changes to get serverless hosting, agentic observability, and governance.
You can deploy your [LlamaIndex](https://www.llamaindex.ai/) projects to Blaxel with minimal code editing (and zero configuration), enabling you to use [Serverless Deployments](../Infrastructure/Global-Inference-Network), [Agentic Observability](../Observability/Overview), [Policies](../Model-Governance/Policies), and more.
## Get started with LlamaIndex on Blaxel
To get started with LlamaIndex on Blaxel:
* if you already have a LlamaIndex agent, adapt your code with [Blaxel SDK commands](../Agents/Develop-an-agent) to connect to [MCP servers](../Functions/Overview), [LLMs](../Models/Overview) and [other agents](../Agents/Overview).
* else initialize an example project in LlamaIndex by using the following Blaxel CLI command and selecting the *LlamaIndex hello world:*
```bash theme={null}
bl new agent
```
[Deploy](../Agents/Deploy-an-agent) it by running:
```bash theme={null}
bl deploy
```
## Develop a LlamaIndex agent using Blaxel features
While building your agent in LlamaIndex, use Blaxel [SDK](../sdk-reference/introduction) to connect to resources already hosted on Blaxel:
* [MCP servers](../Functions/Overview)
* [LLMs](../Models/Overview)
* [other agents](../Agents/Overview)
### Connect to MCP servers
Connect to [MCP servers](../Functions/Overview) using the Blaxel SDK to access pre-built or custom tool servers hosted on Blaxel. This eliminates the need to manage server connections yourself, with credentials stored securely on the platform.
Run the following command to retrieve tools in LlamaIndex format:
```python Python theme={null}
from blaxel.llamaindex import bl_tools
await bl_tools(['mcp-server-name'])
```
```typescript TypeScript theme={null}
import { blTools } from '@blaxel/llamaindex';
const tools = await blTools(['mcp-server-name'])
```
### Connect to LLMs
Connect to [LLMs](../Models/Overview) hosted on Blaxel using the SDK to avoid managing model API connections yourself. All credentials remain securely stored on the platform.
```python Python theme={null}
from blaxel.llamaindex import bl_model
model = await bl_model("model-api-name")
```
```typescript TypeScript theme={null}
import { blModel } from "@blaxel/llamaindex";
const model = await blModel("model-api-name");
```
### Connect to other agents
Connect to other agents hosted on Blaxel from your code by using the [Blaxel SDK](../sdk-reference/introduction). This allows for multi-agent chaining without managing connections yourself. This command is independent of the framework used to build the agent.
```python Python theme={null}
from blaxel.core.agents import bl_agent
response = await bl_agent("agent-name").run(input);
```
```typescript TypeScript theme={null}
import { blAgent } from "@blaxel/core";
const myAgentResponse = await blAgent("agent-name").run(input);
```
### Host your agent on Blaxel
You can [deploy](../Agents/Deploy-an-agent) your agent on Blaxel, enabling you to use [Serverless Deployments](../Infrastructure/Global-Inference-Network), [Agentic Observability](../Observability/Overview), [Policies](../Model-Governance/Policies), and more. This command is independent of the framework used to build the agent.
Either run the following CLI command from the root of your agent repository.
```bash theme={null}
bl deploy
```
Or [connect a GitHub repository to Blaxel](../Agents/Github-integration) for automatic deployments every time you push on *main*.
# Use Mastra agents with Blaxel
Source: https://docs.blaxel.ai/Tutorials/Mastra
Build Mastra TypeScript agents with built-in support for Blaxel sandboxes, giving your agents secure remote execution environments in the cloud.
[Mastra](https://mastra.ai/) is an open source TypeScript framework for building agentic applications. It includes built-in support for Blaxel sandboxes, making it possible to create agents that are backed by remote execution environments (sandboxes) in the cloud.
This tutorial explores how you can use Mastra with Blaxel.
## Prerequisites
* A Blaxel account. If not, [sign up for a Blaxel account](https://blaxel.ai).
* A Blaxel workspace and API key. Learn about [Blaxel workspaces](/Security/Workspace-access-control) and how to [obtain a Blaxel API key](/Security/Access-tokens#api-keys).
* An API key for a [supported LLM provider](https://mastra.ai/models).
* A Node.js development environment.
The Mastra framework is provider-agnostic and can be used with multiple LLM providers. This tutorial uses Anthropic models, but you can also read about [using other models](https://mastra.ai/models).
## 1. Install required dependencies
In your Mastra project directory, initialize a new project and add the Mastra and Blaxel packages, `tsx` to run the TypeScript examples, and Node.js types:
```shell theme={null}
npm init -y
npm install -D typescript @types/node mastra@latest
npm install @mastra/core@latest zod @mastra/blaxel tsx
npm pkg set type=module
npx tsc --init \
--target ES2022 \
--module ES2022 \
--moduleResolution bundler \
--esModuleInterop \
--strict \
--skipLibCheck
```
More information on Mastra installation is available in the [Mastra documentation](https://mastra.ai/docs/getting-started/manual-install).
## 2. Configure the environment
Add your LLM API key, Blaxel API key, Blaxel workspace name and Blaxel deployment region as environment variables:
```shell theme={null}
export ANTHROPIC_API_KEY=your_key_here
export BL_API_KEY=your_key_here
export BL_WORKSPACE=your_workspace_name_here
export BL_REGION=us-pdx-1
```
## 3. Build a simple agent
In your project directory, create a file named `index.ts` with the following code.
```typescript TypeScript theme={null}
import { Agent } from '@mastra/core/agent'
import { Workspace } from '@mastra/core/workspace'
import { BlaxelSandbox } from '@mastra/blaxel'
async function main() {
const sandboxId = `mastra-blaxel-simple-${Date.now()}`
// create sandbox
const workspace = new Workspace({
sandbox: new BlaxelSandbox({
id: sandboxId,
image: 'blaxel/base-image',
memory: 4096,
}),
})
try {
// create agent
const agent = new Agent({
id: 'agent',
name: 'Agent',
instructions: 'You have access to a sandbox environment. You can execute commands, manage files, and inspect processes inside the sandbox.',
model: 'anthropic/claude-sonnet-4-6',
workspace,
})
// give the agent a task
const result = await agent.generate(
'Install a complete Go development environment. Once done, return the installed Go version number and Go environment variables.',
{ maxSteps: 50 }
)
console.log(result.text)
} finally {
// delete the sandbox once complete
await workspace.sandbox?.destroy()
}
}
main().catch((error) => {
console.error(error)
process.exit(1)
})
```
This creates a simple sandbox-backed agent on Blaxel with the Mastra framework. Instructions that you send the agent will be executed in a uniquely named sandbox that is dynamically created and managed in your workspace on Blaxel's infrastructure.
Run the agent:
```shell theme={null}
npx tsx index.ts
```
It will create and deploy a Blaxel sandbox using the `blaxel/base-image` image, then investigate the sandbox and install all the tools required for Go development. Once done, it will return a report of its work and automatically delete the sandbox. Here's a partial example of the output:
```shell theme={null}
Go is installed and ready.
- Installed version: `go1.25.9`
- Command check: `go version` → `go version go1.25.9 linux/amd64`
**Go Env**
- `AR='ar'`
- `CC='cc'`
- `CGO_CFLAGS='-O2 -g'`
- `CGO_CPPFLAGS=''`
- `CGO_CXXFLAGS='-O2 -g'`
- `CGO_ENABLED='1'`
- `CGO_FFLAGS='-O2 -g'`
...
```
## 4. Build a streaming coding agent
One of the most popular uses for agents is to generate code. Sandboxes provide isolated execution environments, allowing agents to securely run generated code with no risk of escaping.
Update `index.ts` to build a simple coding agent that accepts tasks from the command line and streams its responses as it works:
```typescript TypeScript theme={null}
import { Agent } from '@mastra/core/agent'
import { Workspace } from '@mastra/core/workspace'
import { BlaxelSandbox } from '@mastra/blaxel'
// read task from command line
const task = process.argv[2]
if (!task) {
console.error('Usage: npx tsx index.ts ""')
process.exit(1)
}
async function main() {
const sandboxId = `mastra-blaxel-coding-${Date.now()}`
// create sandbox
const workspace = new Workspace({
sandbox: new BlaxelSandbox({
id: sandboxId,
image: 'blaxel/py-app',
memory: 4096,
}),
})
try {
// create agent
const agent = new Agent({
id: 'coding-agent',
name: 'Coding agent',
instructions: `You are an expert software engineer with access to a sandbox environment. You can write, run, and debug code in any language. Use the sandbox to execute code, install dependencies, and verify your solutions work correctly before responding. Install whichever packages you need for your task.`,
model: 'anthropic/claude-sonnet-4-6',
workspace,
})
// set task for agent
// stream agent output
const stream = await agent.stream(task, { maxSteps: 50 })
process.stdout.write('agent > ')
for await (const chunk of stream.textStream) {
process.stdout.write(chunk)
}
process.stdout.write('\n' + '--'.repeat(20) + '\n')
} finally {
// delete sandbox when complete
await workspace.sandbox?.destroy()
}
}
main().catch((error) => {
console.error(error)
process.exit(1)
})
```
This creates a coding agent that accepts requests from the command-line. The coding agent's uniquely named sandbox is generated from Blaxel's `blaxel/py-app` image, which contains a complete Python development environment.
Test the agent:
```shell theme={null}
npx tsx index.ts "Write a Python program to calculate the number of vowels in a word. Show the code. Test it and show the results."
```
The agent will start working on the task in a sandbox, streaming its responses as it works. Here's a partial example of the output:
```shell theme={null}
agent > Here's the Python program to calculate the number of vowels in a word:
def count_vowels(word):
vowels = "aeiouAEIOU"
count = 0
for letter in word:
if letter in vowels:
count += 1
return count
# Test words
test_words = ["hello", "Python", "AEIOU", "rhythm", "beautiful", "sky"]
...
```
That's it! You're ready to start building and deploying Blaxel sandbox-backed agents with Mastra.
## Resources
Want more info on developing and deploying agents on Blaxel? Check out the following resources:
Complete tutorial for deploying AI agents to Blaxel.
Complete tutorial for managing variables and secrets when deploying to Blaxel.
Official Mastra documentation.
# Run Next.js in a sandbox
Source: https://docs.blaxel.ai/Tutorials/Nextjs
Run a Next.js application inside a Blaxel sandbox and expose it securely through preview URLs, with setup steps and end-to-end code examples.
This tutorial explains how to run a Next.js application inside a Blaxel sandbox and expose it securely using sandbox preview URLs.
## Prerequisites
Before starting, ensure you have:
* [Blaxel CLI](../cli-reference/introduction) installed and authenticated (`bl login`)
* Node.js 18+ installed
* `@blaxel/core` package installed in your project (`npm install @blaxel/core`)
## Architecture Overview
Running Next.js inside a Blaxel sandbox requires a few adjustments compared to local development:
* Running the Next.js dev server on port 3000
* Exposing the Next.js dev server via a Blaxel preview URL
## Create a base sandbox image
### Dockerfile
```dockerfile theme={null}
FROM node:22-alpine
RUN apk update && apk add --no-cache \
git \
curl \
netcat-openbsd \
&& rm -rf /var/cache/apk/*
WORKDIR /app
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
# Create Next.js project with TypeScript, Tailwind, App Router, and Turbopack
RUN mkdir -p /app \
&& npx create-next-app@latest /app --use-npm --typescript --eslint --tailwind --src-dir --app --import-alias "@/*" --no-git --yes \
&& cd /app && npm install --save @next/swc-linux-x64-musl
COPY ./next.config.ts /app/next.config.ts
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Add npm global modules to PATH
ENV PATH="/usr/local/bin:$PATH"
ENTRYPOINT ["/entrypoint.sh"]
```
### entrypoint.sh
Create an entrypoint script that starts the sandbox API and the dev server:
```bash theme={null}
#!/bin/sh
# Set environment variables
export PATH="/usr/local/bin:$PATH"
# Start sandbox-api in the background
/usr/local/bin/sandbox-api &
# Function to wait for port to be available
wait_for_port() {
local port=$1
local timeout=30
local count=0
echo "Waiting for port $port to be available..."
while ! nc -z localhost $port; do
sleep 1
count=$((count + 1))
if [ $count -gt $timeout ]; then
echo "Timeout waiting for port $port"
exit 1
fi
done
echo "Port $port is now available"
}
# Wait for port 8080 to be available
wait_for_port 8080
# Execute curl command to start Next.js dev server
echo "Running Next.js dev server..."
curl http://localhost:8080/process \
-X POST \
-H "Content-Type: application/json" \
-d '{
"name": "dev-server",
"workingDir": "/app",
"command": "npm run dev -- --hostname $HOST --port 3000",
"waitForCompletion": false,
"restartOnFailure": true,
"maxRestarts": 25
}'
wait
```
### next.config.ts
Create a `next.config.ts` file to configure Next.js for use with Blaxel previews:
```typescript theme={null}
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
allowedDevOrigins: ["*.preview.bl.run"],
};
export default nextConfig;
```
The `allowedDevOrigins: ["*.preview.bl.run"]` setting allows the dev server to accept requests from the Blaxel preview origin, which is required for Blaxel preview URLs to work correctly. If you have configured a custom domain in your Blaxel workspace, you should also add it to this array (e.g., `["*.preview.bl.run", "*.preview.mycompany.com"]`).
### blaxel.toml
Create a `blaxel.toml` file in the same directory as your Dockerfile:
```toml theme={null}
type = "sandbox"
name = "nextjs-template"
[runtime]
memory = 4096
[[runtime.ports]]
name = "nextjs-dev"
target = 3000
protocol = "tcp"
```
## Deploy the sandbox image
Deploy the image by running:
```bash theme={null}
bl deploy
```
## Create or reuse a sandbox
Create a sandbox from the base image:
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-nextjs-sandbox";
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "nextjs",
},
image: "nextjs-template:latest",
memory: 4096,
});
```
## Configure CORS for preview URL access
Next.js dev servers work well with permissive CORS headers when accessed through a preview URL:
```typescript theme={null}
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
```
Alternatively, you can use [custom domains](https://docs.blaxel.ai/Infrastructure/Custom-domains) to expose previews on your own domain.
## Create the preview URL
Next.js runs on port 3000, so we expose that port via a [preview URL](../Sandboxes/Preview-url):
```typescript theme={null}
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "dev-server-preview" },
spec: {
responseHeaders,
public: false,
port: 3000,
},
});
```
## Generate a preview token
To securely access the preview, a token is required:
```typescript theme={null}
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24); // 1 day
const token = await preview.tokens.create(expiresAt);
```
## Start the dev server
If not using the entrypoint script, you can start the dev server programmatically:
```typescript theme={null}
async function startDevServer(sandbox: SandboxInstance) {
console.log("Starting Next.js dev server...");
await sandbox.process.exec({
name: "dev-server",
command: "npm run dev -- --hostname $HOST --port 3000",
workingDir: "/app",
waitForPorts: [3000],
restartOnFailure: true,
maxRestarts: 25,
});
}
```
## Stream logs
To monitor the Next.js dev server output in real-time:
```typescript theme={null}
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log);
},
});
// When done monitoring, close the stream:
logStream.close();
```
## Access the Next.js application
Once everything is running, the Next.js application will be available at `https://?bl_preview_token=`.
## Complete example
Here is a full example combining all the steps:
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-nextjs-sandbox";
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
async function startDevServer(sandbox: SandboxInstance) {
await sandbox.process.exec({
name: "dev-server",
command: "npm run dev -- --hostname $HOST --port 3000",
workingDir: "/app",
waitForPorts: [3000],
restartOnFailure: true,
maxRestarts: 25,
});
}
async function main() {
try {
// Create or reuse the sandbox
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "nextjs",
},
image: "nextjs-template:latest",
memory: 4096,
});
// Create preview
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "preview" },
spec: {
responseHeaders,
public: false,
port: 3000,
},
});
// Generate preview token
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
const token = await preview.tokens.create(expiresAt);
// Start dev server if not already running
const processes = await sandbox.process.list();
if (!processes.find((p) => p.name === "dev-server")) {
await startDevServer(sandbox);
}
// Print access URL
const webUrl = `${preview.spec?.url}?bl_preview_token=${token.value}`;
console.log(`Next.js Preview URL: ${webUrl}`);
// Stream logs
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log)
},
});
// Keep running until interrupted
process.on("SIGINT", () => {
logStream.close();
process.exit(0);
});
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();
```
## Template features
### Turbopack
The template uses Turbopack, Next.js's Rust-based bundler, for significantly faster development builds. Turbopack provides:
* Faster cold starts
* Instant hot module replacement (HMR)
* Optimized incremental compilation
The `@next/swc-linux-x64-musl` package is pre-installed for optimal performance on Alpine Linux.
### App Router
The template comes pre-configured with the App Router (`/app` directory structure). This provides:
* Server Components by default
* Nested layouts
* Loading and error states
* Server Actions
### TypeScript
Full TypeScript support is enabled out of the box with strict type checking.
### Tailwind CSS
Tailwind CSS is pre-configured for styling. The `src/app/globals.css` file includes the Tailwind directives.
### ESLint
ESLint is configured with Next.js recommended rules for code quality.
# Give an OpenAI agent a computer and keep its work
Source: https://docs.blaxel.ai/Tutorials/OpenAI-Agents-API
Create a report with an OpenAI-hosted agent on Blaxel, inspect the result, and let a fresh session review the saved file through Agent Drive.
Run an OpenAI-hosted agent on a Blaxel computer to turn a source report into a summary. Then use Agent Drive to let a fresh agent review the saved file after the first computer is deleted.
## Prerequisites
* Python 3.11 through 3.14 and Git
* An OpenAI project with Agents API access and two keys in the same organization, project, and owner:
* `OPENAI_API_KEY`, the project key. It stays with your application.
* `OPENAI_EXECUTOR_API_KEY`, a separate restricted executor key created in the same project by the same owner. It is required in every agent mode and is the only OpenAI key passed to worker Sandboxes.
* A Blaxel workspace, using an [installed Blaxel CLI](/cli-reference/introduction) with `bl login`, or provided as `BL_WORKSPACE` and `BL_API_KEY` ([API keys](/Security/Access-tokens#api-keys))
* Public PyPI access to install the cookbook dependencies with `./run.sh`
Agent Drive access in `us-was-1` is optional for the first run and required for the optional fresh-session handoff.
The Agents API is a public beta. This walkthrough uses the public `openai` package and `client.beta.agents`. It is separate from the [OpenAI Agents SDK tutorial](/Tutorials/OpenAI-Agents-SDK).
Create `OPENAI_API_KEY` on the [API keys page](https://platform.openai.com/api-keys) with Agents read/write and Responses write permissions. Create a separate `OPENAI_EXECUTOR_API_KEY` on the [Agents environment-key page](https://platform.openai.com/agents?tab=environments\&environment_view=keys), in the same organization, project, and user or service account as the session. Set all other permissions to None. The environment key permits connecting the executor only; a generic restricted API key with List models access does not establish that permission.
Both keys are required. Missing or identical keys fail before a worker is provisioned. The application key stays outside workers; only the environment key is passed as `CODEX_API_KEY` to `codex exec-server`. Follow the [OpenAI self-hosted setup](https://developers.openai.com/api/docs/guides/agents-api/environments/self-hosted#authentication) for the current dashboard flow.
These examples create hosted resources and invoke a model. Your local script creates a session and a computer, runs the file task, verifies the result, and deletes the temporary resources.
## 1. Configure and run the cookbook
```bash theme={null}
git clone https://github.com/blaxel-ai/blaxel-openai-agents-api-cookbook.git
cd blaxel-openai-agents-api-cookbook
export OPENAI_API_KEY=''
export OPENAI_EXECUTOR_API_KEY=''
export BL_WORKSPACE=''
export BL_API_KEY=''
./run.sh
```
The default `auto` mode uses Agent Drive when available. Set `BL_AGENT_DRIVE_MODE=off` before the run for completely disposable storage.
The launcher creates a virtual environment and installs public dependencies. The source is a fictional billing incident with counts, owners, deadlines, and a missing-data caveat. The agent reads `sample_report.txt` and writes `summary.md`.
OpenAI manages the agent session. Blaxel supplies the computer that runs its commands. The executor connects outbound to OpenAI; the first run requires no webhook deployment or public worker port.
## 2. Open your summary
Open the printed `outputs//summary.md` path in your editor. Check how the agent summarizes the failures, assigns follow-up work, and handles the missing-data caveat.
The script verifies file use before saving the local copy. It also confirms that its new turn completed and both temporary resources were deleted. These are selected output lines; the run also prints the local output and retained Drive paths:
```text theme={null}
final status: idle
confirmed generated file .../summary.md
verified deletion of OpenAI session sess_...
verified deletion of Blaxel sandbox openai-agents-api-...
```
Generated analysis still needs your judgment. The automated check confirms that the agent recovered a fresh verification marker from the source file.
| Resource | After the run |
| - | - |
| OpenAI session and Blaxel computer | Deleted and verified absent or terminated |
| Local summary | Retained in `outputs//summary.md` |
| Agent Drive files | Retained when Drive is enabled |
Without Agent Drive access, the baseline uses temporary storage and still saves your local summary before deleting the computer.
## 3. Continue in a fresh session
Agent Drive becomes most useful when another agent continues from an explicit file instead of copied conversation history.
```bash theme={null}
./run.sh --handoff
```
This command runs the full two-stage example; an earlier baseline run is not required. The first session writes `summary.md`, then its session and Sandbox are deleted. A fresh session and Sandbox mount the same Drive, read that file, and write `review.md`:
```text theme={null}
session A + Sandbox A -> summary.md -> deleted
|
session B + Sandbox B -> review.md -> deleted
```
Both `summary.md` and `review.md` remain in the same Agent Drive run directory. The second agent must read the original verification marker before its review passes.
Agent Drive shares inspectable files. It does not copy model memory, conversation history, or session state.
Open `outputs//summary.md` and `outputs//review.md` from this run. Both local copies share the original run's directory.
The handoff requires Agent Drive in `us-was-1`. It refuses `BL_AGENT_DRIVE_MODE=off` and reports an access or region error when persistence is unavailable.
## 4. Adapt the task
Replace `sample_report.txt` with your own document. Change the first agent's prompt and output check in `main.py`, and the follow-up task in `handoff.py`. Keep a check tied to your input so a completed turn alone cannot pass as useful work.
The cookbook keeps session creation, computer setup, the task, verification, and cleanup visible. The baseline uses one session and computer. Handoff adds a second pair only after the first is gone.
### Prompt your coding agent
After configuring the credentials above, you can give a coding agent this task:
```text theme={null}
Clone https://github.com/blaxel-ai/blaxel-openai-agents-api-cookbook.git and read AGENTS.md.
Check that Python 3.11 through 3.14, OPENAI_API_KEY, a distinct OPENAI_EXECUTOR_API_KEY, and Blaxel credentials are configured. Do not print or save secrets. Stop before creating resources if a prerequisite is missing.
Run ./run.sh without changing source. Open its local outputs//summary.md, report what the agent produced, and confirm that the OpenAI session and worker were deleted. If Agent Drive is available, run ./run.sh --handoff, open the summary and review from that run, and verify that a fresh session and computer continued from the saved file after the first pair was deleted.
Report the local output paths, retained Drive, and cleanup result. If a run fails, use its documented recovery path and report the exact failure. Do not patch source, resend uncertain input, deploy webhooks, or touch unrelated resources.
```
## 5. Review cleanup
The scripts delete their temporary sessions and computers. Local files in `outputs/` and files on Agent Drive are retained intentionally. Inspect or export the Drive's files before deleting it.
For an interrupted run, use the exact printed receipt from the same Blaxel workspace and endpoint:
```bash theme={null}
.venv/bin/python cleanup_run.py .runs/.json
```
Recovery preserves local output and Agent Drive. Verify session and worker deletion in its result. Keep the receipt if cleanup fails; a computer's expiration time is a backstop, not confirmation of cleanup.
### Troubleshooting
| Symptom | Next action |
| - | - |
| Missing key or executor permission error | Check the separate application/environment keys and their organization, project, and owner |
| Agent Drive unavailable | Follow the printed access link; use `BL_AGENT_DRIVE_MODE=off` for the baseline or obtain access for handoff |
| Installation or connection fails | Read the reported process error and exact worker identity; check the cookbook's tested versions |
| Input submission times out | Inspect the recorded session; do not blindly resend input that may already have been accepted |
| Cleanup fails or the target differs | Keep the receipt, select its original workspace and endpoint, and run recovery |
## Resources
Review credentials, storage policy, execution modes, and lifecycle behavior.Adapt the baseline, or explore the separate team and webhook guides.Configure persistent files and access permissions.
# Use OpenAI Agents SDK with sandboxes
Source: https://docs.blaxel.ai/Tutorials/OpenAI-Agents-SDK
Use the OpenAI Agents SDK to create compute-capable Codex agents backed by Blaxel sandboxes for running commands, editing files, and executing code.
The [OpenAI Agents SDK](https://developers.openai.com/api/docs/guides/agents-sdk) is an open source, production-grade library for building agentic applications leveraging the Codex harness. One of the most interesting features of this SDK is the ability to create agents that are backed by remote execution environments (sandboxes) in the cloud. This ability allows the harness to get access to its own computer to execute commands, work with files and directories, write code, and perform computations.
This tutorial explores how you can use the OpenAI Agents SDK with Blaxel to create powerful sandbox-backed agents.
## Prerequisites
* An OpenAI API key. If not, [sign up for an OpenAI account](https://openai.com/) and obtain an API key.
* A Blaxel account. If not, [sign up for a Blaxel account](https://blaxel.ai).
* A Blaxel workspace and API key. Learn about [Blaxel workspaces](/Security/Workspace-access-control) and how to [obtain a Blaxel API key](/Security/Access-tokens#api-keys).
The OpenAI Agents SDK is provider-agnostic and can be used with both OpenAI and non-OpenAI models. This tutorial uses OpenAI models, but you can also read about [integrating other models with the Python SDK](https://openai.github.io/openai-agents-python/models/#non-openai-models).
## 1. Install the Blaxel CLI and log in to Blaxel
[Install the Blaxel CLI](../cli-reference/introduction) and log in to Blaxel using this command:
```bash theme={null}
bl login
```
## 2. Install required dependencies
Create a directory for the project:
```shell theme={null}
mkdir sandbox-agent && cd sandbox-agent
```
In your project directory, install the [OpenAI Agents SDK with Blaxel support](https://developers.openai.com/api/docs/guides/agents-sdk) for the agent loop:
```shell TypeScript (npm) theme={null}
npm init # if new project
npm install @openai/agents-extensions @blaxel/core
```
```shell TypeScript (pnpm) theme={null}
pnpm init # if new project
pnpm install @openai/agents-extensions @blaxel/core
```
```shell TypeScript (yarn) theme={null}
yarn init # if new project
yarn add @openai/agents-extensions @blaxel/core
```
```shell TypeScript (bun) theme={null}
bun init -m --yes # if new project
bun add @openai/agents-extensions @blaxel/core
```
```shell Python theme={null}
python3 -m venv .venv && source .venv/bin/activate # if new project
pip install openai-agents[blaxel]
```
## 3. Configure the environment
Add your OpenAI API key, Blaxel API key and Blaxel workspace name as environment variables:
```shell theme={null}
export OPENAI_API_KEY=your_key_here
export BL_API_KEY=your_key_here
export BL_WORKSPACE=your_workspace_name_here
```
## 4. Build a simple agent
In your project directory, create a file named `index.ts` (TypeScript) or `main.py` (Python) with the following code.
```typescript TypeScript theme={null}
import { run } from "@openai/agents";
import { Manifest, SandboxAgent } from "@openai/agents/sandbox";
import { BlaxelSandboxClient } from "@openai/agents-extensions/sandbox/blaxel";
async function main() {
const agent = new SandboxAgent({
name: "Sandboxed agent",
model: "gpt-5.4",
instructions:
"You have access to a sandbox environment. You can execute commands, manage files, and inspect processes inside the sandbox.",
modelSettings: { toolChoice: "auto" },
defaultManifest: new Manifest({ root: "/blaxel" }),
});
const client = new BlaxelSandboxClient();
const result = await run(
agent,
"Install a complete Go development environment. Once done, return the installed Go version number and Go environment variables.",
{
maxTurns: 50,
sandbox: {
client,
options: {
name: "agent-sandbox-01",
image: "blaxel/base-image",
region: "us-pdx-1",
memory: 4096,
},
},
}
);
console.log(result.finalOutput);
}
await main().catch(console.error);
```
```python Python theme={null}
import asyncio
from agents import ModelSettings, Runner
from agents.run import RunConfig
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
from agents.extensions.sandbox import BlaxelSandboxClient, BlaxelSandboxClientOptions
async def main():
agent = SandboxAgent(
name="Sandboxed agent",
model="gpt-5.4",
instructions=(
"You have access to a sandbox environment. You can execute commands, manage files, and inspect processes inside the sandbox."
),
model_settings=ModelSettings(tool_choice="auto"),
default_manifest=Manifest(root="/blaxel"),
)
client = BlaxelSandboxClient()
run_config = RunConfig(
sandbox=SandboxRunConfig(
client=client,
options=BlaxelSandboxClientOptions(
name="agent-sandbox-01",
image="blaxel/base-image",
region="us-pdx-1",
memory=4096,
),
)
)
result = await Runner.run(agent, "Install a complete Go development environment. Once done, return the installed Go version number and Go environment variables.", run_config=run_config)
print(result.final_output)
await client.close()
if __name__ == "__main__":
asyncio.run(main())
```
This creates a simple sandbox-backed agent with the OpenAI Agents SDK. Instructions that you send the agent will be executed in a sandbox named `agent-sandbox-01` that is dynamically created and managed in your workspace on Blaxel's infrastructure.
Run the agent. It will create and deploy a Blaxel sandbox using the `blaxel/base-image` image, then investigate the sandbox and install all the tools required for Go development. Once done, it will return a report of its work and automatically delete the sandbox. Here's a partial example of the output:
```shell theme={null}
Go is installed and ready.
- Installed version: `go1.25.9`
- Command check: `go version` → `go version go1.25.9 linux/amd64`
**Go Env**
- `AR='ar'`
- `CC='cc'`
- `CGO_CFLAGS='-O2 -g'`
- `CGO_CPPFLAGS=''`
- `CGO_CXXFLAGS='-O2 -g'`
- `CGO_ENABLED='1'`
- `CGO_FFLAGS='-O2 -g'`
...
```
## 5. Build a data analysis agent
In addition to running commands, agents can also create, delete and manage files and directories in the sandbox filesystem. Generated files can be transferred from the sandbox environment to the host (and vice-versa).
To see this in action, update the simple agent from the previous example to perform data processing and analysis in a sandbox:
```typescript TypeScript theme={null}
import { run } from "@openai/agents";
import { SandboxAgent } from "@openai/agents/sandbox";
import { BlaxelSandboxClient } from "@openai/agents-extensions/sandbox/blaxel";
import { writeFileSync } from "fs";
async function main() {
const client = new BlaxelSandboxClient();
const session = await client.create({
options: {
name: "agent-sandbox-02",
image: "blaxel/py-app",
region: "us-pdx-1",
memory: 4096,
},
});
const agent = new SandboxAgent({
name: "Data analyst",
model: "gpt-5.4",
instructions:
"You are a data analyst. You have access to a sandbox environment. You can execute commands, manage files, and inspect processes inside the sandbox.",
});
const stream = await run(
agent,
"Obtain sample telemetry data using the API https://api.datamock.dev/v1/iot-telemetry?quantity=10&deviceType=wearable_fitness_tracker&exclude=deviceId,ipAddress,macAddress,signalStrength,networkType,location,status,batteryLevel,lastSync,firmwareVersion,alerts." +
"Analyze the received data and create a chart showing memory and CPU usage. Save it as /workspace/chart.png. " +
"Install any packages you need.",
{
stream: true,
maxTurns: 50,
sandbox: { session },
}
);
process.stdout.write("assistant> ");
stream.toTextStream({ compatibleWithNodeStreams: true }).pipe(process.stdout);
await stream.completed;
process.stdout.write("\n");
const chartData = await session.readFile({ path: "/workspace/chart.png" });
writeFileSync("chart.png", chartData);
console.log("Saved chart to host as chart.png");
await session.close();
await session.shutdown();
}
await main().catch(console.error);
```
```python Python theme={null}
import asyncio
from pathlib import Path
from openai.types.responses import ResponseTextDeltaEvent
from agents import Runner
from agents.run import RunConfig
from agents.sandbox import SandboxAgent, SandboxRunConfig
from agents.extensions.sandbox import BlaxelSandboxClient, BlaxelSandboxClientOptions
async def main():
client = BlaxelSandboxClient()
session = await client.create(
options=BlaxelSandboxClientOptions(
name="agent-sandbox-02",
image="blaxel/py-app",
region="us-pdx-1",
memory=4096,
)
)
await session.start()
agent = SandboxAgent(
name="Data analyst",
model="gpt-5.4",
instructions="You are a data analyst. You have access to a sandbox environment. You can execute commands, manage files, and inspect processes inside the sandbox.",
)
stream_result = Runner.run_streamed(
agent,
(
"Obtain sample telemetry data using the API https://api.datamock.dev/v1/iot-telemetry?quantity=10&deviceType=wearable_fitness_tracker&exclude=deviceId,ipAddress,macAddress,signalStrength,networkType,location,status,batteryLevel,lastSync,firmwareVersion,alerts."
"Analyze the received data and create a chart showing memory and CPU usage. Save it as /workspace/chart.png. "
"Install any packages you need."
),
run_config=RunConfig(sandbox=SandboxRunConfig(session=session)),
max_turns=50,
)
saw_text_delta = False
async for event in stream_result.stream_events():
if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent):
if not saw_text_delta:
print("assistant> ", end="", flush=True)
saw_text_delta = True
print(event.data.delta, end="", flush=True)
if saw_text_delta:
print()
with open("chart.png", "wb") as f:
f.write((await session.read(Path("/workspace/chart.png"))).read())
print(f"Saved chart to host as chart.png")
await session.stop()
await session.shutdown()
await client.close()
if __name__ == "__main__":
asyncio.run(main())
```
In this example, the agent uses Blaxel's `blaxel/py-app` image to create a sandbox containing a complete Python development environment. When the sandbox is created, the SDK automatically also creates a new workspace directory (defaults to `/workspace` but can be configured to point to another location) for the agent to operate in.
The agent then uses sandbox tools to call the remote API, obtains a dataset (mock data), and autonomously installs all the required packages for chart creation. It then prepares and saves a chart of the received data to the workspace at `/workspace/chart.png`. This file is then read from the sandbox session and downloaded to the local host. Once the session is complete, the sandbox is terminated.
Run the agent - once complete, you will have a new `chart.png` file in your project directory containing the generated chart. This example also demonstrates the SDK's streaming capabilities - you will see a stream of the agent's reasoning as it works.
## 6. Build a coding agent
One of the most popular uses for agents is to generate code. Sandboxes provide isolated execution environments, allowing agents to securely run generated code with no risk of escaping. Blaxel sandboxes offer a unique advantage here: human operators can preview agent-generated applications in real-time via direct preview URLs for each running sandbox.
To see this in practice, update the agent code in `index.ts` / `main.py` as below:
```typescript TypeScript theme={null}
import { run } from "@openai/agents";
import { Manifest, SandboxAgent } from "@openai/agents/sandbox";
import { urlForExposedPort } from "@openai/agents-core/sandbox";
import { BlaxelSandboxClient } from "@openai/agents-extensions/sandbox/blaxel";
async function main() {
const task = process.argv.slice(2).join(" ").trim();
if (!task) {
console.log("Usage: node index.ts ");
process.exit(1);
}
const client = new BlaxelSandboxClient();
const session = await client.create({
manifest: new Manifest({ root: "/blaxel" }),
options: {
name: "agent-sandbox-03",
image: "blaxel/nextjs:latest",
region: "us-pdx-1",
memory: 4096,
exposedPortPublic: true,
pauseOnExit: true,
ttl: "30m",
},
});
const agent = new SandboxAgent({
name: "Next.js code generation agent",
model: "gpt-5.4",
instructions:
"You are a Next.js expert with shell access to a sandbox. You can execute commands, manage files, and start servers. The sandbox includes a skeleton Next.js app in /blaxel.",
modelSettings: { toolChoice: "auto" },
});
try {
const stream = await run(agent, task, {
stream: true,
maxTurns: 50,
sandbox: { session },
});
process.stdout.write("assistant> ");
stream.toTextStream({ compatibleWithNodeStreams: true }).pipe(process.stdout);
await stream.completed;
process.stdout.write("\n");
const endpoint = await session.resolveExposedPort(3000);
const previewUrl = urlForExposedPort(endpoint, "http");
console.log(`Preview URL: ${previewUrl}`);
} finally {
await session.close();
await session.shutdown();
}
}
await main().catch(console.error);
```
```python Python theme={null}
import asyncio
import sys
from openai.types.responses import ResponseTextDeltaEvent
from agents import ModelSettings, Runner
from agents.run import RunConfig
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
from agents.extensions.sandbox import BlaxelSandboxClient, BlaxelSandboxClientOptions
async def main():
task = " ".join(sys.argv[1:]).strip()
if not task:
print("Usage: python main.py ")
sys.exit(1)
client = BlaxelSandboxClient()
session = await client.create(
manifest=Manifest(root="/blaxel"),
options=BlaxelSandboxClientOptions(
name="agent-sandbox-03",
image="blaxel/nextjs:latest",
region="us-pdx-1",
memory=4096,
exposed_port_public=True,
pause_on_exit=True,
ttl="30m"
)
)
agent = SandboxAgent(
name="Next.js code generation agent",
model="gpt-5.4",
instructions="You are a Next.js expert with shell access to a sandbox. You can execute commands, manage files, and start servers. The sandbox includes a skeleton Next.js app in /blaxel.",
model_settings=ModelSettings(tool_choice="auto"),
)
try:
stream_result = Runner.run_streamed(
agent,
task,
run_config=RunConfig(sandbox=SandboxRunConfig(session=session)),
max_turns=50,
)
saw_text_delta = False
async for event in stream_result.stream_events():
if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent):
if not saw_text_delta:
print("assistant> ", end="", flush=True)
saw_text_delta = True
print(event.data.delta, end="", flush=True)
if saw_text_delta:
print()
endpoint = await session.resolve_exposed_port(3000)
preview_url = endpoint.url_for("http")
print(f"Preview URL: {preview_url}")
finally:
await session.stop()
await session.shutdown()
await client.close()
if __name__ == "__main__":
asyncio.run(main())
```
This creates a Next.js coding agent that accepts requests from the command-line. The coding agent's sandbox is generated from Blaxel's `blaxel/nextjs` image, which contains a complete Next.js development environment and a Next.js skeleton application. Notice the `ttl` parameter, which sets an [expiration policy](/Sandboxes/Expiration) to automatically delete the sandbox after 30 minutes.
In addition, the `exposed_port_public` parameter and the `resolve_exposed_port()` function call take care of producing a [preview URL](/Sandboxes/Preview-url) for the user, and the `pause_on_exit` parameter prevents the sandbox from being automatically deleted at the end of the session.
Test the agent:
```shell TypeScript theme={null}
# Node.js
node index.ts "My cat Kitty does amazing things. Create a website about her."
# Bun
bun index.ts "My cat Kitty does amazing things. Create a website about her."
```
```shell Python theme={null}
python main.py "My cat Kitty does amazing things. Create a website about her."
```
The agent will start working on the task in a sandbox, streaming its responses as it works. Once it is complete, you should see a preview URL similar to the following:
```shell theme={null}
Preview URL: https://a4455b03....preview.bl.run/
```
Open the preview URL in your browser to see the results of the agent's work.
That's it! You're ready to start building and deploying Blaxel sandbox-backed agents with OpenAI Agents SDK on Blaxel.
## Resources
Want more info on developing and deploying agents on Blaxel? Check out the following resources:
You don’t have to stop at the sandboxes! Deploy OpenAI Agents SDK on Blaxel.
Complete tutorial for deploying AI agents to Blaxel.
Complete tutorial for managing variables and secrets when deploying to Blaxel.
# Deploy OpenAI Agents SDK on Blaxel
Source: https://docs.blaxel.ai/Tutorials/OpenAI-Agents-SDK-Deployment
Deploy an OpenAI Agents SDK agent to Blaxel Agents Hosting as a serverless auto-scalable API and colocate it close to the sandboxes it works on.
The [OpenAI Agents SDK](https://developers.openai.com/api/docs/guides/agents-sdk) is an open source, production-grade library for building agentic applications leveraging the Codex harness. One of the most interesting features of this SDK is the ability to create agents that are backed by remote execution environments (sandboxes) in the cloud. This ability allows the harness to get access to its own computer to execute commands, work with files and directories, write code, and perform computations.
This tutorial explores how you can deploy your OpenAI Agents SDK projects to Blaxel with minimal code editing (and zero configuration), enabling you to colocate them close to the [sandboxes](/Sandboxes/Overview) the agents work on.
## Prerequisites
* An OpenAI API key. If not, [sign up for an OpenAI account](https://openai.com/) and obtain an API key.
* A Blaxel account. If not, [sign up for a Blaxel account](https://blaxel.ai).
* A Blaxel workspace and API key. Learn about [Blaxel workspaces](/Security/Workspace-access-control) and how to [obtain a Blaxel API key](/Security/Access-tokens#api-keys).
The OpenAI Agents SDK is provider-agnostic and can be used with both OpenAI and non-OpenAI models. This tutorial uses OpenAI models, but you can also read about [integrating other models](https://openai.github.io/openai-agents-python/models/#non-openai-models).
## 1. Install the Blaxel CLI and log in to Blaxel
The main way to deploy an agent on Blaxel is with the [Blaxel CLI](../cli-reference/introduction). This method is detailed in this tutorial. Alternatively you can [connect a GitHub repository](/Agents/Github-integration) - any push to the `main` branch of the repository will automatically update the deployment on Blaxel - or deploy from a variety of pre-built templates using the Blaxel Console.
[Install the Blaxel CLI](../cli-reference/introduction) and log in to Blaxel using this command:
```bash theme={null}
bl login
```
## 2. Install required dependencies
Create a directory for the project:
```shell theme={null}
mkdir sandbox-agent && cd sandbox-agent
```
Agents deployed on Blaxel Agents Hosting must expose an HTTP endpoint for requests.
In your project directory, install the [OpenAI Agents SDK with Blaxel support](https://developers.openai.com/api/docs/guides/agents-sdk) for the agent loop and [Express](https://expressjs.com/) (TypeScript) / [FastAPI](https://fastapi.tiangolo.com/) (Python) to handle HTTP requests and responses:
```shell TypeScript (npm) theme={null}
npm init # if new project
npm install @openai/agents-extensions @blaxel/core express @types/express tsx typescript
```
```shell TypeScript (pnpm) theme={null}
pnpm init # if new project
pnpm install @openai/agents-extensions @blaxel/core express @types/express tsx typescript
```
```shell TypeScript (yarn) theme={null}
yarn init # if new project
yarn add @openai/agents-extensions @blaxel/core express @types/express tsx typescript
```
```shell TypeScript (bun) theme={null}
bun init -m --yes # if new project
bun add @openai/agents-extensions @blaxel/core express @types/express
```
```shell Python (pip) theme={null}
python3 -m venv .venv && source .venv/bin/activate # if new project
pip install openai-agents[blaxel] fastapi uvicorn
```
## 3. Configure the environment
Add your OpenAI API key, Blaxel API key and Blaxel workspace name to a `.env` file in the project directory:
```shell theme={null}
echo "OPENAI_API_KEY=your_key_here" > .env
echo "BL_API_KEY=your_key_here" >> .env
echo "BL_WORKSPACE=your_workspace_name_here" >> .env
```
## 4. Build a simple agent
In your project directory, create a file named `main.py` (Python) or `index.ts` (TypeScript) with the following code:
```typescript TypeScript theme={null}
import { run } from "@openai/agents";
import { Manifest, SandboxAgent } from "@openai/agents/sandbox";
import { BlaxelSandboxClient } from "@openai/agents-extensions/sandbox/blaxel";
import express from "express";
const app = express();
app.use(express.json());
const host = process.env.HOST ?? "::";
const port = parseInt(process.env.PORT ?? "8000");
app.post("/query", async (req, res) => {
const { task } = req.body;
const client = new BlaxelSandboxClient();
const agent = new SandboxAgent({
name: "Sandbox agent",
model: "gpt-5.4",
instructions:
"You have access to a minimal sandbox environment. You can execute commands, manage files, and inspect processes inside the sandbox.",
modelSettings: { toolChoice: "auto" },
defaultManifest: new Manifest({ root: "/blaxel" }),
});
const result = await run(agent, task, {
maxTurns: 50,
sandbox: {
client,
options: {
name: "agent-sandbox",
image: "blaxel/base-image",
region: "us-pdx-1",
memory: 4096,
},
},
});
res.json({ result: result.finalOutput });
});
app.listen(port, host, () => {
console.log(`Server listening on ${host}:${port}`);
});
```
```python Python theme={null}
import os
import uvicorn
from fastapi import FastAPI, Request
from agents import ModelSettings, Runner
from agents.run import RunConfig
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
from agents.extensions.sandbox import BlaxelSandboxClient, BlaxelSandboxClientOptions
app = FastAPI()
host = os.getenv("HOST", "::")
port = int(os.getenv("PORT", "8000"))
@app.post("/query")
async def query(request: Request):
body = await request.json()
task = body["task"]
client = BlaxelSandboxClient()
run_config = RunConfig(
sandbox=SandboxRunConfig(
manifest=Manifest(root="/blaxel"),
client=client,
options=BlaxelSandboxClientOptions(
name="agent-sandbox",
image="blaxel/base-image",
region="us-pdx-1",
memory=4096,
),
)
)
agent = SandboxAgent(
name="Sandbox agent",
model="gpt-5.4",
instructions="You have access to a minimal sandbox environment. You can execute commands, manage files, and inspect processes inside the sandbox.",
model_settings=ModelSettings(tool_choice="auto"),
default_manifest=Manifest(root="/blaxel"),
)
result = await Runner.run(agent, task, run_config=run_config)
return {"result": result.final_output}
if __name__ == "__main__":
print(f"Server listening on {host}:{port}")
uvicorn.run(app, host=host, port=port)
```
This creates a simple sandbox-backed agent with the OpenAI Agents SDK. Queries that you send the agent will be executed in a sandbox named `agent-sandbox` that is dynamically created and managed in your workspace on Blaxel's infrastructure.
The agent's HTTP service must be bound to the host and port provided by Blaxel. Blaxel automatically injects these values as `HOST` and `PORT` variables into the runtime environment. It is important to read these variables in your code and ensure that the agent's HTTP service binds to the correct host and port.
In TypeScript, entrypoints are managed in the `scripts` section of the `package.json` file. Update your `package.json` to ensure that `start` and `dev` scripts are defined in the `scripts` section (TypeScript only).
```json TypeScript (npm/pnpm/yarn) theme={null}
{
"scripts": {
"start": "tsx index.ts",
"dev": "tsx --watch index.ts",
"build": "tsc"
},
// ...
}
```
```json TypeScript (bun) theme={null}
{
"scripts": {
"start": "bun run index.ts",
"dev": "bun --watch index.ts"
},
// ...
}
```
## 5. Enable telemetry (optional)
Instrumentation happens automatically when workloads run on Blaxel. To enable telemetry:
* Add the required package to your project:
```shell TypeScript (npm) theme={null}
npm install @blaxel/telemetry
```
```shell TypeScript (pnpm) theme={null}
pnpm install @blaxel/telemetry
```
```shell TypeScript (yarn) theme={null}
yarn add @blaxel/telemetry
```
```shell TypeScript (bun) theme={null}
bun add @blaxel/telemetry
```
```shell Python (pip) theme={null}
pip install "blaxel[telemetry]"
```
* Import the package in your code:
```typescript TypeScript theme={null}
import "@blaxel/telemetry";
```
```python Python theme={null}
import blaxel.telemetry
```
## 6. Test the agent locally
Test the agent by making the endpoint available locally:
```shell theme={null}
bl serve --hotreload &
```
This starts the agent locally while handling the core agent logic, function calls and model API calls exactly as they would be when deployed on Blaxel. The `--hotreload` flag monitors and reloads the agent if the source code changes.
Note the host port on which the agent is running.
In another terminal, send the agent a request (update the endpoint URL below with the correct port number for your agent):
```shell theme={null}
curl -X POST http://0.0.0.0:8000/query \
-H "Content-Type: application/json" \
-d '{"task": "Install a complete Go development environment. Return the Go version and Go environment variables."}'
```
The agent will create and deploy a Blaxel sandbox using the `blaxel/base-image` image, then investigate the sandbox and install all the tools required for Go development. Once done, it will return a report of its work and automatically delete the sandbox.
## 7. Deploy the agent on Blaxel
You're now ready to deploy the agent on Blaxel. When deploying to Blaxel, your workloads are served optimally to dramatically accelerate cold-start and latency while enforcing your deployment policies.
For Python, create a `requirements.txt` file in the project directory with the following dependencies:
```shell Python theme={null}
fastapi
uvicorn
openai-agents[blaxel]
```
For TypeScript, your `package.json` already contains all required dependencies from the `npm install` commands run earlier. No additional file is needed.
Then, deploy the agent by running the following command:
```shell theme={null}
bl deploy
```
The Blaxel CLI will prompt for the type of resource you are deploying ("agent"). It will then auto-detect other details of your project and begin the deployment. The deployment process usually takes a few minutes, and you can watch progress from the Blaxel Console.
Once the deployment process is complete, log in to the [Blaxel Console](https://app.blaxel.ai/) to find the global endpoint for your agent service. Typically, this will be of the form `https://run.blaxel.ai/WORKSPACE/agents/AGENT`.
## 8. Test the agent on Blaxel
By default, agents deployed on Blaxel are not public. All agent requests must be authenticated via a [bearer token](/Security/Access-tokens). Requests can be made either via the Blaxel API or the Blaxel CLI.
Test the deployed agent by sending an authenticated request to its global endpoint (update the endpoint URL below with the correct endpoint URL for your agent, and modify the `task` as desired):
```bash theme={null}
curl -X POST https://run.blaxel.ai/$(bl workspace --current)/agents/sandbox-agent/query \
-H "X-Blaxel-Workspace: $(bl workspace --current)" \
-H "X-Blaxel-Authorization: Bearer $(bl token)" \
-H "Content-Type: application/json" \
-d '{"task": "Write a Python program to count how many times each word appears in a sentence. Test it. Return the test results and the final working code."}'
```
As before, the agent will create a sandbox, generate the code and provide the result.
You can also send a request through the Blaxel CLI:
```shell theme={null}
bl run agent sandbox-agent/query --data '{"task": "Write a Python program to count how many times each word appears in a sentence. Test it. Return the test results and the final working code."}'
```
Although deployed agents are private by default, it is possible to [make an agent publicly available](/Agents/Query-agents#make-an-agent-public).
That's it! You're ready to start building and deploying Blaxel sandbox-backed agents with OpenAI Agents SDK on Blaxel.
## Resources
Want more info on developing and deploying agents on Blaxel? Check out the following resources:
Build compute-capable agents backed by Blaxel sandboxes using OpenAI Agents SDK.
Complete tutorial for deploying AI agents to Blaxel.
Complete tutorial for managing variables and secrets when deploying to Blaxel.
# Run OpenClaw on Blaxel
Source: https://docs.blaxel.ai/Tutorials/OpenClaw
Deploy and run OpenClaw, an open-source coding agent harness, inside a Blaxel sandbox with persistent volume storage and live preview URLs.
This tutorial explains how to deploy and run [OpenClaw](https://openclaw.ai) on Blaxel.
OpenClaw can be manually installed and configured to run inside a Blaxel sandbox.
## Prerequisites
Before starting, ensure you have:
* a [Blaxel account](https://blaxel.ai)
* an API key from any of the [supported model providers](https://docs.openclaw.ai/concepts/model-providers)
* a Python development environment
## Create a sandbox
1. [Download and install the Blaxel CLI](https://docs.blaxel.ai/cli-reference/introduction#install) and log in to your Blaxel account:
```shell theme={null}
bl login
```
2. In a new directory, install the Blaxel [Python SDK](https://github.com/blaxel-ai/sdk-python) (there's also a [TypeScript SDK](https://github.com/blaxel-ai/sdk-typescript)):
```shell theme={null}
python3 -m venv .venv
source .venv/bin/activate
pip install blaxel
```
3. Create a script named `main.py` in the same directory:
```python theme={null}
import asyncio
import os
import sys
from datetime import datetime, timedelta, UTC
from blaxel.core import SandboxInstance
async def main():
# Create sandbox
sandbox = await SandboxInstance.create_if_not_exists({
"name": "openclaw-sandbox",
"image": "blaxel/node:latest",
"memory": 4096,
"region": "us-pdx-1",
})
# Create preview
preview = await sandbox.previews.create_if_not_exists({
"metadata": {"name": "openclaw-gateway"},
"spec": {
"port": 18789,
"public": False,
}
})
# Create preview token
# Valid for 24 hours
expires_at = datetime.now(UTC) + timedelta(minutes=1440)
token = await preview.tokens.create(expires_at)
# Get preview URL and token
print(f"Preview URL: {preview.spec.url}")
print(f"Token: {token}")
if __name__ == "__main__":
asyncio.run(main())
```
This script:
* creates a new Blaxel sandbox named `openclaw-sandbox` using Blaxel's Node.js base image;
* opens the sandbox port 18789, which OpenClaw uses for WebSocket and HTTP connections;
* creates a preview URL for the service running on that port; and
* creates an access token for the preview URL, valid for 24 hours.
4. Run the script to create the sandbox and preview URL:
```shell theme={null}
python main.py
```
Once complete, the script displays the generated preview URL (for example, `https://b186....preview.bl.run`) and preview URL access token (for example, `cbba622560db78e...`). Note these values, as you will require them in subsequent steps.
## Install and configure OpenClaw
1. Connect to the Blaxel sandbox terminal:
```shell theme={null}
bl connect sandbox openclaw-sandbox
```
2. Execute the following commands to install OpenClaw in the sandbox:
```shell theme={null}
apk add curl bash make cmake g++ build-base linux-headers jq
npm install -g openclaw@latest
```
For detailed installation instructions, refer to the [OpenClaw documentation](https://docs.openclaw.ai/install).
3. Configure OpenClaw:
```shell theme={null}
openclaw onboard
```
Read and accept the security warning to proceed.
Select the **Quickstart** mode to proceed. You will be prompted for more information, including choosing a model provider, channels, skills and hooks.
At minimum, you must select a model provider and model and enter the required API key. All other steps are optional and can be skipped if you don't have the details yet.
4. Once the configuration process is complete, OpenClaw will display status output. This will usually include a message that `systemd` services are unavailable. This is expected as, for performance reasons, Blaxel sandboxes do not include the `systemd` process manager.
5. The status output also displays a tokenized dashboard link. Note the dashboard token (for example, `e782efff66...`), as it will be required in the next step.
## Configure OpenClaw Gateway access
1. Edit the OpenClaw configuration file at `/blaxel/.openclaw/openclaw.json` and add the preview URL to the list of allowed origins:
```json theme={null}
"gateway": {
"controlUi": {
"allowedOrigins": ["https://b186....preview.bl.run"]
},
// ...
}
```
An alternative way to do this is with `jq`:
```shell theme={null}
jq '.gateway.controlUi = {"allowedOrigins":["https://b186....preview.bl.run"]}' /blaxel/.openclaw/openclaw.json > /blaxel/.openclaw/openclaw.json.new && mv /blaxel/.openclaw/openclaw.json.new /blaxel/.openclaw/openclaw.json
```
2. Start the OpenClaw Gateway service manually, and keep it running.
```shell theme={null}
openclaw gateway --bind lan --verbose
```
Confirm that you see log messages like the ones below about the Gateway service starting and listening for requests:
```shell theme={null}
14:25:22 [gateway] agent model: google/gemini-2.5-flash-preview-09-2025
14:25:22 [gateway] listening on ws://0.0.0.0:18789 (PID 1660)
14:25:22 [gateway] log file: /tmp/openclaw/openclaw-2026-02-06.log
14:25:22 [ws] → event health seq=1 clients=0 presenceVersion=1 healthVersion=2
```
**IMPORTANT: This is the OpenClaw Gateway process. Make sure that it is running for the rest of these instructions.**
3. Browse to the sandbox preview URL, remembering to also include the preview URL access token in the URL string - for example, `https://b186....preview.bl.run?bl_preview_token=cbba...`. This displays the OpenClaw Control UI.
4. Navigate to the **Overview** page and enter the dashboard token in the **Gateway Token** field.
5. The Control UI also displays the error `pairing required`. This is because the OpenClaw Gateway requires a [one-time pairing approval](https://docs.openclaw.ai/web/control-ui#device-pairing-first-connection) for connections from a new browser/device.
6. Connect to the sandbox terminal **in a separate terminal window** (to not kill the gateway process running):
```shell theme={null}
bl connect sandbox openclaw-sandbox
```
7. List the available pairing requests:
```shell theme={null}
openclaw devices list
```
8. Typically, there will only be one pending pairing request. Approve it using its request identifier:
```shell theme={null}
openclaw devices approve f06d4e9b...
```
9. Browse to the sandbox preview URL again. The Control UI should now be fully functional and ready to accept requests, verified by the health check in the top right corner.
## Test OpenClaw
Navigate to the **Chat** page, enter a prompt, and wait for a reply to confirm that the OpenClaw agent is working:
## Configure OpenClaw channels
OpenClaw supports multiple channels. To enable them, configure them through the Control UI after connecting, or add them to the `openclaw.json` file.
As mentioned earlier, the OpenClaw Gateway process must remain running while you interact with the agent. However, if you end your interactive shell session with the Blaxel sandbox, the Gateway process will terminate automatically as well. To keep the Gateway process running even if you're not in an active shell session with the sandbox, create and execute the following script:
```python theme={null}
import asyncio
import os
import sys
from blaxel.core import SandboxInstance
async def main():
# Get sandbox
sandbox = await SandboxInstance.get("openclaw-sandbox")
# Start process
process = await sandbox.process.exec({
"name": "start-openclaw-gateway",
"command": "openclaw gateway --bind lan --verbose",
"restart_on_failure": True,
"max_restarts": 5
})
if __name__ == "__main__":
asyncio.run(main())
```
# Run NVIDIA OpenShell on Blaxel
Source: https://docs.blaxel.ai/Tutorials/OpenShell
Deploy NVIDIA OpenShell on Blaxel to run policy-confined agents in isolated microVM sandboxes with audited network access.
Run [NVIDIA OpenShell](https://github.com/NVIDIA/OpenShell) on Blaxel to confine autonomous agents with network, filesystem, and process policies. A control sandbox hosts the OpenShell gateway, Blaxel compute driver, and supervisors, while each agent runs in a separate Blaxel microVM.
Your computer only runs the `openshell` CLI. The workload sandbox does not receive your Blaxel key, gateway key, or model-provider credentials.
```mermaid theme={null}
flowchart LR
CLI["openshell CLI (your computer)"] -->|"mTLS over WebSocket (os-tunnel dial)"| GW
subgraph CTRL["Blaxel control sandbox"]
GW["OpenShell gateway"] -->|"ComputeDriver gRPC"| D["openshell-driver-blaxel"]
D -->|spawns| SUP["openshell-supervisor one per sandbox"]
SUP <--> GW
end
D -->|"Blaxel SDK: create, exec, files"| WL
SUP -->|"Sandbox Protocol, TLS 1.3 over a multiplexed WebSocket"| T
subgraph WL["Blaxel workload sandbox"]
T["os-tunnel serve"] --> S["openshell-sandbox no capabilities, loopback only"]
S --> A["agent, uid 1500"]
end
SUP -->|"policy-allowed egress only"| NET["api.github.com, ..."]
```
## Prerequisites
* A [Blaxel account](https://blaxel.ai) and workspace with access to the `landlock`, `tun`, and `iptables` kernel variants
* A service-account API key for the workspace
* The [`bl` CLI](https://github.com/blaxel-ai/toolkit), logged in to your workspace
* [Go](https://go.dev/doc/install) `1.25` or newer
* The [GitHub CLI](https://cli.github.com/) and Git
This tutorial uses OpenShell `main` from the NVIDIA `dev` release. The verified integration uses OpenShell commit `08548713c`, release `0.0.117-dev.281`, the `openshell.compute.v1` compute-driver protocol, and Blaxel Go SDK `v0.27.2`.
OpenShell `main` can change between `dev` releases. The deployment downloads the expected source and verifies its checksums before building.
## 1. Configure the deployment
Clone the Blaxel OpenShell integration repository:
```bash theme={null}
git clone git@github.com:blaxel-ai/openshell-blaxel.git
cd openshell-blaxel
```
Create your local environment file:
```bash theme={null}
cp .env.example .env
```
Open `.env` and set:
* `BL_WORKSPACE` to your Blaxel workspace name
* `BL_ENV` to the target Blaxel environment
* `BL_REGION` to the region where the sandboxes run
* `BL_API_KEY` to the workspace service-account API key
Treat `.env` as a secret and do not commit it. The deployment uses the key from the control plane, but does not place it inside workload sandboxes.
## 2. Deploy the control plane
Build OpenShell and create the Blaxel control sandbox:
```bash theme={null}
make deploy
```
The deployment:
* downloads OpenShell `main` from NVIDIA's `dev` release and checks its checksums
* builds the Blaxel compute driver and WebSocket tunnel
* creates the control sandbox with `tun` and `iptables` enabled
* generates the gateway public key infrastructure
* starts the gateway, driver, ingress tunnel, CLAT, and DNS forwarder
* downloads the CLI client bundle to `~/.openshell-blaxel/mtls`
Running `make deploy` again updates the binaries in place and restarts the gateway and driver.
Blaxel sandboxes are IPv6-only with NAT64/DNS64, and only HTTPS leaves the platform. OpenShell's policy DNS currently resolves A records only, so the control sandbox runs a 464XLAT CLAT with `tayga` and a DNS-over-HTTPS forwarder.
Blaxel exposes HTTPS and WebSocket ingress through `/port/N`. The CLI and Sandbox Protocol therefore use multiplexed WebSockets, with TLS preserved end to end.
The upstream change in [NVIDIA/OpenShell pull request #3702](https://github.com/NVIDIA/OpenShell/pull/3702), tracked by [issue #3716](https://github.com/NVIDIA/OpenShell/issues/3716), removes the need for the CLAT after it is merged.
## 3. Connect the OpenShell CLI
Start the local tunnel and register the CLI with the gateway:
```bash theme={null}
make connect
```
The tunnel carries mTLS traffic from the local CLI to the control sandbox over WebSocket. OpenShell's CLI uses a separate configuration directory, so this integration does not modify an existing OpenShell installation.
## 4. Verify the deployment
Run the end-to-end test suite:
```bash theme={null}
make e2e
```
The suite takes about four minutes. It creates a workload sandbox, verifies the isolation and network-policy boundaries, exercises stop and start behavior, and then deletes the sandbox.
A successful run ends with output similar to:
```text theme={null}
== 1. control plane on Blaxel gateway main (mTLS) · blaxel driver negotiated
== 2. create sandbox Ready after 15s (supervisor session attached)
== 3. isolation uid 1500 · landlock kernel · netns has only loopback
raw egress blocked · Landlock denies /var/tmp
driver state and credentials hidden from the workload
== 4. network policy unlisted host denied · policy update loaded
allowed host reachable through the supervisor proxy
L7 read-only blocks POST · denials in the audit log
== 5. stop / start new generation, fresh launch credentials, workspace kept
== 6. delete gone
RESULT: 20 passed, 0 failed
```
Each workload uses the `landlock` kernel variant with Linux 6.18 and Landlock ABI v7. The agent runs as UID `1500` with no capabilities, `no_new_privs`, Landlock, seccomp, and a network namespace that only contains loopback. Policy-approved egress passes through its supervisor.
## 5. Create and enter a workload sandbox
Create a sandbox named `dev` and keep its main process running:
```bash theme={null}
make os ARGS='sandbox create --name dev --no-tty -- sleep infinity'
```
Open an interactive shell:
```bash theme={null}
make os ARGS='sandbox exec -n dev --tty -- bash -l'
```
The command after `--` is the sandbox's main process. `sleep infinity` keeps the sandbox alive while interactive shells connect and disconnect.
## 6. Apply a read-only network policy
Export the sandbox's base policy:
```bash theme={null}
make os ARGS='policy get dev --base' | sed -n '/^---$/,$p' | sed 1d > /tmp/dev.yaml
```
Add a rule that allows only `/usr/bin/curl` to send read-only REST requests to `api.github.com`:
```bash theme={null}
cat >> /tmp/dev.yaml <<'EOF'
network_policies:
github:
name: github-readonly
endpoints:
- host: api.github.com
port: 443
protocol: rest
enforcement: enforce
access: read-only
binaries:
- path: /usr/bin/curl
EOF
```
Apply the policy and wait for it to load:
```bash theme={null}
make os ARGS='policy set dev --policy /tmp/dev.yaml --wait'
```
From the workload sandbox, `curl https://api.github.com/zen` now succeeds. A `POST` request is denied at HTTP layer 7, and hosts that are not listed in the policy do not resolve.
OpenShell `main` currently emits `policy get --base` output without a trailing newline. The blank line before `network_policies` keeps the appended YAML valid.
## 7. Inspect and manage the deployment
Read the sandbox's OCSF allow and deny audit trail:
```bash theme={null}
make os ARGS='logs dev'
```
Inspect the control-plane processes, gateway, and OpenShell-to-Blaxel sandbox mapping:
```bash theme={null}
make status
```
Preview cleanup actions without changing resources:
```bash theme={null}
./gw/cleanup.sh dev
./gw/cleanup.sh --orphans
```
Add `--apply` to a cleanup command after reviewing its exact plan.
Redeploying restarts the gateway and driver. A workload sandbox pins its first supervisor, so a sandbox that was running before a redeploy needs a new generation. If it appears as `Stopped`, start it again:
```bash theme={null}
make os ARGS='sandbox start dev'
```
The workspace is preserved across stop and start operations.
## 8. Delete the resources
Delete the workload sandbox:
```bash theme={null}
make os ARGS='sandbox delete dev'
```
Delete the control sandbox when you no longer need the deployment:
```bash theme={null}
make destroy
```
## Resources
Review the integration source, design guide, security boundaries, failure handling, and teardown procedures.
Learn about OpenShell policies, architecture, and runtime isolation.
Learn how to create and operate isolated microVM compute environments.
# Tutorials and examples overview
Source: https://docs.blaxel.ai/Tutorials/Overview
Browse Blaxel tutorials and examples covering popular templates, sandbox apps like Next.js and code-server, agent frameworks, and full-repo GitHub templates.
Find the right guide for what you want to build on Blaxel. Start from a popular template, run an app in a sandbox, ship an agent with your preferred framework, or clone a full-repo example.
Deploy Claude, Codex, and other ready-made templates on Blaxel.
Run web apps like Next.js, Astro, Expo, and code-server inside Blaxel sandboxes.
Build and host agents with LangChain, CrewAI, OpenClaw, AI SDK, and more.
Browse complete GitHub templates you can use as reference or deploy directly.
# Run PydanticAI on Blaxel
Source: https://docs.blaxel.ai/Tutorials/PydanticAI
Deploy PydanticAI agent projects to Blaxel with minimal code changes to get serverless hosting, agentic observability, and governance policies.
You can deploy your [PydanticAI](https://ai.pydantic.dev/) projects to Blaxel with minimal code editing (and zero configuration), enabling you to use [Serverless Deployments](../Infrastructure/Global-Inference-Network), [Agentic Observability](../Observability/Overview), [Policies](../Model-Governance/Policies), and more.
## Get started with PydanticAI on Blaxel
To get started with PydanticAI on Blaxel:
* if you already have a PydanticAI agent, adapt your code with [Blaxel SDK commands](../Agents/Develop-an-agent) to connect to [MCP servers](../Functions/Overview), [LLMs](../Models/Overview) and [other agents](../Agents/Overview).
* else initialize an example project in PydanticAI by using the following Blaxel CLI command and selecting the *PydanticAI hello world:*
```bash theme={null}
bl new agent
```
[Deploy](../Agents/Deploy-an-agent) it by running:
```bash theme={null}
bl deploy
```
## Develop a PydanticAI agent using Blaxel features
While building your agent in PydanticAI, use Blaxel [SDK](../sdk-reference/introduction) to connect to resources already hosted on Blaxel:
* [MCP servers](../Functions/Overview)
* [LLMs](../Models/Overview)
* [other agents](../Agents/Overview)
### Connect to MCP servers
Connect to [MCP servers](../Functions/Overview) using the Blaxel SDK to access pre-built or custom tool servers hosted on Blaxel. This eliminates the need to manage server connections yourself, with credentials stored securely on the platform.
Run the following command to retrieve tools in PydanticAI format:
```python Python theme={null}
from blaxel.pydantic import bl_tools
await bl_tools(['mcp-server-name'])
```
### Connect to LLMs
Connect to [LLMs](../Models/Overview) hosted on Blaxel using the SDK to avoid managing model API connections yourself. All credentials remain securely stored on the platform.
```python Python theme={null}
from blaxel.pydantic import bl_model
model = await bl_model("model-api-name")
```
### Connect to other agents
Connect to other agents hosted on Blaxel from your code by using the [Blaxel SDK](../sdk-reference/introduction). This allows for multi-agent chaining without managing connections yourself. This command is independent of the framework used to build the agent.
```python Python theme={null}
from blaxel.core.agents import bl_agent
response = await bl_agent("agent-name").run(input);
```
### Host your agent on Blaxel
You can [deploy](../Agents/Deploy-an-agent) your agent on Blaxel, enabling you to use [Serverless Deployments](../Infrastructure/Global-Inference-Network), [Agentic Observability](../Observability/Overview), [Policies](../Model-Governance/Policies), and more. This command is independent of the framework used to build the agent.
Either run the following CLI command from the root of your agent repository.
```bash theme={null}
bl deploy
```
Or [connect a GitHub repository to Blaxel](../Agents/Github-integration) for automatic deployments every time you push on *main*.
# Sandbox application tutorials overview
Source: https://docs.blaxel.ai/Tutorials/Sandboxes-Overview
Browse tutorials for running web applications like Astro, Next.js, Expo, code-server, Docker, and Tailscale inside Blaxel sandboxes.
Blaxel lets you run Web applications inside a Blaxel sandbox and preview the content in real time using a browser client. This is ideal for preview environments, internal demos, and AI-powered coding workflows built on Blaxel.
Run Astro in a sandbox.
Run Expo in a sandbox.
Run Next.js in a sandbox.
Run code-server in a sandbox.
Run Docker in a sandbox.
Run Tailscale in a sandbox.
# Run Tailscale in a sandbox
Source: https://docs.blaxel.ai/Tutorials/Tailscale
Connect a Blaxel sandbox to your Tailscale tailnet so you can SSH into it from any authorized device, with step-by-step setup instructions.
The Blaxel Tailscale image includes Tailscale and connects to your tailnet when the sandbox starts. Provide a Tailscale authentication key to access the sandbox from any authorized device on your tailnet.
## Prerequisites
Before starting, ensure you have:
* a [Blaxel account](https://blaxel.ai)
* a [Tailscale account](https://tailscale.com/)
* a Tailscale authentication key
* the [Blaxel CLI](/cli-reference/introduction), logged in to your workspace
* the Blaxel TypeScript or Python SDK installed in your project
## 1. Configure a Tailscale authentication key
Follow the [Tailscale authentication key documentation](https://tailscale.com/docs/features/access-control/auth-keys) to create a key.
Then set it as an environment variable:
```sh theme={null}
export TS_AUTHKEY=tskey-auth-...
```
Treat `TS_AUTHKEY` as a secret. Do not commit it to source control or include
it directly in application code.
## 2. Create the sandbox through the API (recommended)
The image uses userspace networking. You do not need to enable a TUN device or `iptables`.
Use the TypeScript or Python SDK to call the Blaxel API. Create the sandbox with the `blaxel/tailscale:latest` image and pass `TS_AUTHKEY` as a runtime environment variable.
The sandbox stops immediately when `TS_AUTHKEY` is missing. An invalid, expired,
or revoked key prevents Tailscale from connecting, so the sandbox stops when
Tailscale exits or after the 60-second startup timeout.
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const authKey = process.env.TS_AUTHKEY;
if (!authKey) throw new Error("TS_AUTHKEY is required");
await SandboxInstance.create({
name: "tailscale-sandbox",
image: "blaxel/tailscale:latest",
envs: [{ name: "TS_AUTHKEY", value: authKey }],
});
```
```python Python theme={null}
import asyncio
import os
from blaxel.core import SandboxInstance
async def main():
auth_key = os.environ["TS_AUTHKEY"]
await SandboxInstance.create({
"name": "tailscale-sandbox",
"image": "blaxel/tailscale:latest",
"envs": [{"name": "TS_AUTHKEY", "value": auth_key}],
})
asyncio.run(main())
```
## 3. Verify the Tailscale connection
Connect to the sandbox terminal:
```sh theme={null}
bl connect sandbox tailscale-sandbox
```
Check that the sandbox is connected and retrieve its Tailscale IP address:
```sh theme={null}
tailscale status
tailscale ip
```
The image uses the first available hostname value in this order: `TS_HOSTNAME`, a manually provided `SANDBOX_NAME`, the Blaxel-provided `BL_NAME`, then `tailscale-sandbox`.
In the standard Blaxel flow, the Tailscale hostname defaults to the sandbox name through `BL_NAME`. Set `TS_HOSTNAME` when creating the sandbox to use a different hostname.
## 4. Connect with Tailscale SSH
From another authorized device on your tailnet, connect with the sandbox hostname or Tailscale IP:
```sh theme={null}
tailscale ssh root@tailscale-sandbox
```
Your [Tailscale SSH access policy](https://tailscale.com/kb/1193/tailscale-ssh) determines which users and devices can connect.
## Appendix: Configure Tailscale manually
### Create a sandbox
This manual setup requires iptables, which is not enabled in sandboxes by default. You enable it by passing [`extraArgs` at creation time](/Sandboxes/Overview#kernel-networking).
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.create({
name: "tailscale-sandbox",
extraArgs: { iptables: "enabled" },
});
```
```python Python theme={null}
from blaxel.core import SandboxInstance
sandbox = await SandboxInstance.create({
"name": "tailscale-sandbox",
"extra_args": {"iptables": "enabled"},
})
```
### Install and configure Tailscale in the sandbox
Connect to the sandbox terminal:
```sh theme={null}
bl connect sandbox tailscale-sandbox
export TS_AUTHKEY=tskey-auth-...
```
Next, install the `tailscale` and `iptables` packages and start the `tailscaled` daemon as a background process.
```sh theme={null}
# Install dependencies
apk add tailscale iptables
# Start the daemon
tailscaled &
```
You can then run `tailscale up --ssh` to authenticate and enable Tailscale SSH.
```sh theme={null}
# Authenticate and enable Tailscale SSH
# Prints an auth URL if no key is provided, or authenticates silently with a key
tailscale up --hostname=my-sandbox --ssh
# or: tailscale up --authkey=$TS_AUTHKEY --hostname=my-sandbox --ssh
```
Retrieve the Tailscale IP.
```sh theme={null}
# Get your Tailscale IP
tailscale ip
```
### Connect to the sandbox using Tailscale
Once authenticated, the sandbox is reachable via SSH from any device on your Tailscale network:
```sh theme={null}
ssh root@
# or using the hostname directly:
ssh root@my-sandbox
```
### Using the SDK
It's also possible to create a sandbox and configure Tailscale using the Blaxel SDKs instead of the sandbox terminal:
```typescript TypeScript theme={null}
import { SandboxInstance } from "@blaxel/core";
const TS_AUTHKEY = process.env.TS_AUTHKEY!;
const SANDBOX_NAME = "tailscale-sandbox";
// 1. Create sandbox with iptables enabled
const sandbox = await SandboxInstance.create({
name: SANDBOX_NAME,
extraArgs: { iptables: "enabled" },
});
// 2. Install dependencies
await sandbox.process.exec({
name: "install-deps",
command: "apk add --no-cache tailscale iptables",
waitForCompletion: true,
timeout: 60, // 60 seconds
});
// 3. Start the tailscaled daemon in the background
await sandbox.process.exec({
name: "tailscaled",
command: "tailscaled",
keepAlive: true,
timeout: 0, // run indefinitely
});
// Give the daemon a moment to initialize
await new Promise((r) => setTimeout(r, 2000));
// 4. Authenticate and bring up the interface
const up = await sandbox.process.exec({
name: "tailscale-up",
command: `tailscale up --authkey=$TS_AUTHKEY --hostname=${SANDBOX_NAME} --ssh`,
env: { TS_AUTHKEY },
waitForCompletion: true,
timeout: 30, // 30 seconds
});
console.log("tailscale up:", up.logs);
// 5. Get the Tailscale IP
const ip = await sandbox.process.exec({
name: "tailscale-ip",
command: "tailscale ip",
waitForCompletion: true,
timeout: 10, // 10 seconds
});
console.log("Tailscale IP:", ip.logs?.trim());
```
```python Python theme={null}
import asyncio
import os
from blaxel.core import SandboxInstance
TS_AUTHKEY = os.environ["TS_AUTHKEY"]
SANDBOX_NAME = "tailscale-sandbox"
async def setup_tailscale():
# 1. Create sandbox with iptables enabled
sandbox = await SandboxInstance.create({
"name": SANDBOX_NAME,
"extra_args": {"iptables": "enabled"},
})
# 2. Install dependencies
await sandbox.process.exec({
"name": "install-deps",
"command": "apk add --no-cache tailscale iptables",
"wait_for_completion": True,
"timeout": 60, # 60 seconds
})
# 3. Start the tailscaled daemon in the background
await sandbox.process.exec({
"name": "tailscaled",
"command": "tailscaled",
"keep_alive": True,
"timeout": 0, # run indefinitely
})
# Give the daemon a moment to initialize
await asyncio.sleep(2)
# 4. Authenticate and bring up the interface
up = await sandbox.process.exec({
"name": "tailscale-up",
"command": f"tailscale up --authkey=$TS_AUTHKEY --hostname={SANDBOX_NAME} --ssh",
"env": {"TS_AUTHKEY": TS_AUTHKEY},
"wait_for_completion": True,
"timeout": 30, # 30 seconds
})
print("tailscale up:", up.logs)
# 5. Get the Tailscale IP
ip = await sandbox.process.exec({
"name": "tailscale-ip",
"command": "tailscale ip",
"wait_for_completion": True,
"timeout": 10, # 10 seconds
})
print("Tailscale IP:", ip.logs.strip())
asyncio.run(setup_tailscale())
```
## Resources
Create and manage Blaxel sandboxes.
Configure reusable and ephemeral authentication keys.
# Run AI SDK on Blaxel
Source: https://docs.blaxel.ai/Tutorials/Vercel-AI
Deploy Vercel AI SDK agent projects to Blaxel with minimal code changes to get serverless hosting, agentic observability, and governance policies.
You can deploy your [AI SDK](https://sdk.vercel.ai/docs/introduction) projects to Blaxel with minimal code editing (and zero configuration), enabling you to use [Serverless Deployments](../Infrastructure/Global-Inference-Network), [Agentic Observability](../Observability/Overview), [Policies](../Model-Governance/Policies), and more.
## Get started with AI SDK on Blaxel
To get started with AI SDK on Blaxel:
* if you already have an AI SDK agent, adapt your code with [Blaxel SDK commands](../Agents/Develop-an-agent) to connect to [MCP servers](../Functions/Overview), [LLMs](../Models/Overview) and [other agents](../Agents/Overview).
* else initialize an example project with AI SDK by using the following Blaxel CLI command and selecting the *Vercel AI hello world:*
```bash theme={null}
bl new agent
```
[Deploy](../Agents/Deploy-an-agent) it by running:
```bash theme={null}
bl deploy
```
## Develop an AI SDK agent using Blaxel features
While building your agent with AI SDK, use Blaxel [SDK](../sdk-reference/introduction) to connect to resources already hosted on Blaxel:
* [MCP servers](../Functions/Overview)
* [LLMs](../Models/Overview)
* [other agents](../Agents/Overview)
### Connect to MCP servers
Connect to [MCP servers](../Functions/Overview) using the Blaxel SDK to access pre-built or custom tool servers hosted on Blaxel. This eliminates the need to manage server connections yourself, with credentials stored securely on the platform.
Run the following command to retrieve tools in AI SDK format:
```typescript TypeScript theme={null}
import { blTools } from '@blaxel/vercel';
const tools = await blTools(['mcp-server-name'])
```
### Connect to LLMs
Connect to [LLMs](../Models/Overview) hosted on Blaxel using the SDK to avoid managing model API connections yourself. All credentials remain securely stored on the platform.
```typescript TypeScript theme={null}
import { blModel } from "@blaxel/vercel";
const model = await blModel("model-api-name");
```
### Connect to other agents
Connect to other agents hosted on Blaxel from your code by using the [Blaxel SDK](../sdk-reference/introduction). This allows for multi-agent chaining without managing connections yourself. This command is independent of the framework used to build the agent.
```typescript TypeScript theme={null}
import { blAgent } from "@blaxel/core";
const myAgentResponse = await blAgent("agent-name").run(input);
```
### Host your agent on Blaxel
You can [deploy](../Agents/Deploy-an-agent) your agent on Blaxel, enabling you to use [Serverless Deployments](../Infrastructure/Global-Inference-Network), [Agentic Observability](../Observability/Overview), [Policies](../Model-Governance/Policies), and more. This command is independent of the framework used to build the agent.
Either run the following CLI command from the root of your agent repository.
```bash theme={null}
bl deploy
```
Or [connect a GitHub repository to Blaxel](../Agents/Github-integration) for automatic deployments every time you push on *main*.
# Orchestrate Blaxel agents with n8n
Source: https://docs.blaxel.ai/Tutorials/n8n
Build n8n workflows that forward chat messages to Blaxel-hosted AI agents via HTTP requests, with step-by-step setup and configuration.
This tutorial will walk you through how to integrate your AI agents —deployed on Blaxel— into automated workflows using [n8n](https://n8n.io/). Whether you’re new to Blaxel, n8n, or both, this tutorial will help you get started quickly with a minimalistic setup that you can build on.
## What You’ll Build
This is a simple n8n workflow that:
1. listens for chat messages,
2. then forwards those messages as inputs to your [AI agent on Blaxel](../Agents/Overview) via an HTTP request.
Here's a minimal JSON snippet that demonstrates the workflow:
```json theme={null}
{
"name": "Demo: My first AI Agent in n8n",
"nodes": [
{
"parameters": {
"options": {}
},
"id": "5b410409-5b0b-47bd-b413-5b9b1000a063",
"name": "When chat message received",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"typeVersion": 1.1,
"position": [660, -200],
"webhookId": "a889d2ae-2159-402f-b326-5f61e90f602e"
},
{
"parameters": {
"method": "POST",
"url": "https://run.blaxel.ai/{YOUR-WORKSPACE}/agents/{YOUR-AGENT}",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "inputs",
"value": "={{ $json.chatInput }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1040, -200],
"id": "d389abf6-09cd-4fad-88fa-4a8c098bddf5",
"name": "HTTP Request",
"credentials": {
"httpHeaderAuth": {
"id": "{YOUR_AUTH_ACCOUNT_ID}",
"name": "Header Auth account"
}
}
}
],
"pinData": {},
"connections": {
"When chat message received": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request": {
"main": [
[]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "f82cb549-fa06-4cbe-9268-76451dd8e7fc",
"meta": {
"templateId": "PT1i+zU92Ii5O2XCObkhfHJR5h9rNJTpiCIkYJk9jHU=",
"templateCredsSetupCompleted": true,
"instanceId": "b90a39a88ba2a73793446bbe14503ff3b070f8a0ec6fce01ee5b4761919441e1"
},
"id": "Xu7ugYZKH0Dzn9hQ",
"tags": []
}
```
## Step 1: Update the URL Field
Before running your workflow, **update the URL field** in the HTTP Request node to match your [agent’s URL](../Agents/Query-agents). Replace `https://run.blaxel.ai/{YOUR-WORKSPACE}/agents/{YOUR-AGENT}` with your actual workspace and agent identifiers.
## Step 2: Configure Header Authentication
To secure your API calls, you must set up header authentication. Follow these two key steps:
1. **Set up the header auth credentials:**
Ensure that your HTTP Request node is set in ***Header Auth*** type.
2. **Create Credentials:**
Fill out the form with the following details. For more details on obtaining your Blaxel API key, refer to this [Access Tokens documentation](../Security/Access-tokens#api-keys).
* **Name:** `Authorization`
* **Value:** `Bearer `
Your n8n workflow is ready to launch!
Hooking up your Blaxel AI agents with n8n is like giving your dev toolkit superpowers! This bare-bones setup we just walked through is just scratching the surface. Think of it as your "Hello World" moment before diving into the really cool stuff - like building a workflow of multiple AI agents that work together.
# Volumes
Source: https://docs.blaxel.ai/Volumes/Overview
Attach Blaxel volumes to sandboxes and other resources for persistent storage that survives recreation, enabling stateful environments.
Blaxel Volumes provide **persistent storage that survives resource destruction and recreation**, enabling stateful environments and data retention across lifecycle events.
While Blaxel automatically snapshots the full state of a sandbox at scale down and stores it in warm storage for ultra-fast boot times, volumes offer a more cost-effective solution to persist files for weeks to years. Use [volume templates](/Volumes/Volumes-templates) to start from a pre-populated filesystem.
Volumes use block storage and can only be accessed by attaching them to a running sandbox or agent. They cannot be mounted locally on your machine. Blaxel also does not support mounting external storage (such as S3 buckets) as volumes.
Refer to our [blog post on choosing the right storage for your Blaxel agents](https://blaxel.ai/blog/choose-the-right-storage-for-your-blaxel-agents) for more information.
## Create a volume
To create a standalone volume, you must provide a unique `name` and specify its `size` in megabytes (MB). You can also specify optional labels. This volume exists independently of any resource it may later be attached to.
The Blaxel SDK requires two environment variables to authenticate:
| Variable | Description |
| - | - |
| `BL_WORKSPACE` | Your Blaxel workspace name |
| `BL_API_KEY` | Your Blaxel API key |
You can create an API key from the [Blaxel console](https://app.blaxel.ai/profile/security). Your workspace name is visible in the URL when you log in to the console (e.g. `app.blaxel.ai/{workspace}`).
Set them as environment variables or add them to a `.env` file at the root of your project:
```bash theme={null}
export BL_WORKSPACE=my-workspace
export BL_API_KEY=my-api-key
```
When developing locally, you can also **log in to your workspace with Blaxel CLI** (as shown above). This allows you to run Blaxel SDK functions that will automatically connect to your workspace without additional setup. When you deploy on Blaxel, authentication is handled automatically — no environment variables needed.
```typescript TypeScript theme={null}
import { VolumeInstance } from "@blaxel/core";
const volume = await VolumeInstance.create({
name: "my-volume",
size: 2048, // in MB
region: "us-pdx-1",
});
```
```python Python theme={null}
from blaxel.core import VolumeInstance
volume = await VolumeInstance.create({
"name": "my-volume",
"size": 2048, # in MB
"region": "us-pdx-1",
})
```
```bash Blaxel CLI theme={null}
bl apply -f - <
You can create a volume from a [template](/Volumes/Volumes-templates) to automatically pre-populate it with files.
```typescript TypeScript theme={null}
const volume = await VolumeInstance.createIfNotExists({
name: "myvolume",
template: "mytemplate:1", // Use template-name:revision or template-name:latest
region: "us-pdx-1",
});
```
```python Python theme={null}
volume = await VolumeInstance.create_if_not_exists({
"name": "myvolume",
"template": "mytemplate:1", # Use template-name:revision or template-name:latest
"region": "us-pdx-1",
})
```
```bash Blaxel CLI theme={null}
bl apply -f - <
Labels are specified as key-value pairs during volume creation.
```typescript TypeScript theme={null}
const volume = await VolumeInstance.create({
name: "my-volume",
size: 2048,
region: "us-pdx-1",
labels: { env: "test", project: "12345" },
});
```
```python Python theme={null}
volume = await VolumeInstance.create({
"name": "my-volume",
"size": 2048,
"region": "us-pdx-1",
"labels": {"env": "test", "project": "12345"},
})
```
```bash Blaxel CLI theme={null}
bl apply -f - <
## Delete a volume
Delete a volume by calling:
* the class-level `delete()` method with the volume `name` as argument, or
```typescript TypeScript theme={null}
import { VolumeInstance } from "@blaxel/core";
await VolumeInstance.delete("my-volume");
```
```python Python theme={null}
from blaxel.core import VolumeInstance
await VolumeInstance.delete("my-volume")
```
* by calling the instance-level `delete()` method:
```typescript TypeScript theme={null}
import { VolumeInstance } from "@blaxel/core";
const volume = await VolumeInstance.get("my-volume");
await volume.delete()
```
```python Python theme={null}
from blaxel.core import VolumeInstance
volume = await VolumeInstance.get("my-volume")
await volume.delete()
```
## Resize a volume
Currently, it is only possible to increase the volume size (not decrease it).
Resize a volume by calling:
* the class-level `update()` method with the volume `name` and new size as argument, or
```typescript TypeScript theme={null}
import { VolumeInstance } from "@blaxel/core";
const updatedVolume = await VolumeInstance.update("my-volume", { size: 1024 });
```
```python Python theme={null}
from blaxel.core import VolumeInstance
updated_volume = await VolumeInstance.update("my-volume", { "size": 1024 })
```
* by calling the instance-level `update()` method with the new size as argument:
```typescript TypeScript theme={null}
import { VolumeInstance } from "@blaxel/core";
const volume = await VolumeInstance.get("my-volume");
const updatedVolume = await volume.update({ size: 1024 });
```
```python Python theme={null}
from blaxel.core import VolumeInstance
volume = await VolumeInstance.get("my-volume")
updated_volume = await volume.update({ "size": 1024 })
```
## List volumes
To retrieve all volumes in your workspace, use the built-in SDK pagination helper functions.
Pagination is recommended for list operations because retrieving all available resources in a single request is slow and resource-intensive, and therefore does not scale well.
```typescript TypeScript theme={null}
import { VolumeInstance } from "@blaxel/core";
const volumes = await VolumeInstance.list({ limit: 50 });
for await (const volume of volumes) {
console.log(volume.name);
}
```
```python Python theme={null}
import asyncio
from blaxel.core import VolumeInstance
async def main():
volumes = await VolumeInstance.list(limit=50)
async for volume in volumes.auto_paging_iter():
print(volume.name)
asyncio.run(main())
```
```shell Blaxel CLI theme={null}
bl get volumes
```
For more information on the SDK pagination helpers, refer to the [SDK reference documentation](/sdk-reference/introduction#pagination).
Pagination is also available via the Management API list endpoint.
Request the first page:
```bash theme={null}
curl 'https://api.blaxel.ai/v0/volumes?limit=50' \
-H 'X-Blaxel-Authorization: Bearer YOUR-API-KEY' \
-H 'Blaxel-Version: 2026-04-28'
```
The response includes a `data` array and a `meta` object:
```json theme={null}
{
"data": [ ... ],
"meta": {
"hasMore": true,
"nextCursor": "...",
"total": 18
}
}
```
Pass `meta.nextCursor` as `cursor` on the next request and repeat until `meta.hasMore` is `false`:
```bash theme={null}
curl 'https://api.blaxel.ai/v0/volumes?limit=50&cursor=NEXT_CURSOR' \
-H 'X-Blaxel-Authorization: Bearer YOUR-API-KEY' \
-H 'Blaxel-Version: 2026-04-28'
```
For accounts with multiple workspaces, specify the workspace as an additional request parameter, such as `?workspace=WORKSPACE_NAME&...`, or via an additional `X-Blaxel-Workspace: WORKSPACE_NAME` header in the request.
For more information, refer to the [API reference documentation](/api-reference/introduction#pagination).
You can use labels for filtering volumes in the Blaxel CLI or Blaxel Console:
```shell theme={null}
# Get volumes with specific label (e.g., env=test)
bl get volumes -o json | jq -r '.[] | select(.metadata.labels.env == "test") | .metadata.name'
```
## Use volumes with sandboxes and agents
Attach persistent storage to a sandbox at creation time.
Attach persistent storage to a deployed agent via `blaxel.toml`.
# Volume templates
Source: https://docs.blaxel.ai/Volumes/Volumes-templates
Create volume templates pre-populated with files, dependencies, or datasets so new Blaxel sandboxes and resources start with environments ready to use.
Volume templates let you create **pre-populated [volumes](Overview) with files and directories**, improving copy performance by up to 90% compared to `cp -r`. Use them to build development environments with pre-installed dependencies, datasets, or application code.
## Create a volume template
### Initialize a new template
To create a new volume template, use the following command:
```bash theme={null}
bl new volume-template mytemplate
```
This creates a folder named `mytemplate` containing a `blaxel.toml` file with the following configuration:
```toml theme={null}
type = "volume-template"
directory = "." # Root of your volume - everything at and below this level will be copied
defaultSize = 2048 # Default size in MB for volumes created from this template
```
The `directory` field specifies which folder's contents will be included in the template:
* `"."` (default) - Includes everything in the template folder
* `"app"` - Includes only files inside the `app` subdirectory
* `"src/data"` - Includes only files inside the `src/data` subdirectory
For example, if you want to include only your application code without configuration files:
```toml theme={null}
type = "volume-template"
directory = "app" # Only files inside the 'app' folder will be copied
defaultSize = 2048
```
### Deploy a volume template
Deploy your volume template to make it available for creating other volumes:
```bash theme={null}
bl deploy
```
To preview the deployment without making changes:
```bash theme={null}
bl deploy --dryrun
```
Each deployment creates a new revision of your template. Revisions are auto-incremented and versioned for rollback capabilities.
## Use volume templates
Once deployed, you can create volumes from your template:
```typescript TypeScript theme={null}
const volume = await VolumeInstance.createIfNotExists({
name: "myvolume",
template: "mytemplate:1" // Use template-name:revision or template-name:latest
});
```
```python Python theme={null}
volume = await VolumeInstance.create_if_not_exists({
"name": "myvolume",
"template": "mytemplate:1" # Use template-name:revision or template-name:latest
})
```
Override the default volume size if needed:
```typescript TypeScript theme={null}
const volume = await VolumeInstance.createIfNotExists({
name: "myvolume",
template: "mytemplate:latest",
size: 4096 // Override the default size if needed
});
```
```python Python theme={null}
volume = await VolumeInstance.create_if_not_exists({
"name": "myvolume",
"template": "mytemplate:latest",
"size": 4096 # Override the default size if needed
})
```
Template revisions are incremental and auto-generated. The system maintains a limited number of versions for each template.
## Manage volume templates
List all available templates in your workspace:
```bash theme={null}
bl get volumetemplates
```
View details of a specific template, including size requirements:
```bash theme={null}
bl get volumetemplate mytemplate
```
### Size constraints
Volumes must be provisioned with sufficient space for the template content. If a volume is smaller than the template data, creation will fail with an error.
Always add a delta (extra space) to your volume size beyond the template content size. This ensures you can add files to the volume after it's attached. We recommend provisioning at least 20-30% extra space.
To check your template directory size before deployment:
```bash theme={null}
du -sh mytemplate
```
For example, if your template is 1.5GB, create volumes with at least 2GB of space:
```typescript TypeScript theme={null}
const volume = await VolumeInstance.createIfNotExists({
name: "myvolume",
template: "mytemplate:latest",
size: 2048 // 2GB for a 1.5GB template - leaves 500MB for new files
});
```
```python Python theme={null}
volume = await VolumeInstance.create_if_not_exists({
"name": "myvolume",
"template": "mytemplate:latest",
"size": 2048 # 2GB for a 1.5GB template - leaves 500MB for new files
})
```
This helps you set an appropriate `defaultSize` in your `blaxel.toml` or specify the correct size when creating volumes.
### Delete a template
To delete a volume template and all its associated revisions:
```bash theme={null}
bl delete volumetemplate mytemplate
```
Deleting a template does not affect existing volumes created from that template. However, you won't be able to create new volumes from deleted templates.
## Limitations
Volume templates **do not handle symlinks or hardlinks**, which may cause unexpected behavior. In addition, symlinks pointing outside the template (for example, `symlink -> /etc/hosts`) are explicitly forbidden. In these cases, use a copy instead.
As a result, using package managers like `pnpm` or `uv` may also behave unexpectedly if dependencies are vendored within the template.
## Use volume templates with sandboxes and agents
Create and attach pre-populated volumes to sandboxes.
Create and attach pre-populated volumes to agents.
# Create agent
Source: https://docs.blaxel.ai/api-reference/agents/create-agent
/api-reference/controlplane.yml post /agents
Creates a new AI agent deployment from your code. The agent will be built and deployed as a serverless auto-scaling endpoint. Use the Blaxel CLI 'bl deploy' for a simpler deployment experience.
# Delete agent
Source: https://docs.blaxel.ai/api-reference/agents/delete-agent
/api-reference/controlplane.yml delete /agents/{agentName}
Permanently deletes an agent and all its deployment history. The agent's inference endpoint will immediately stop responding. This action cannot be undone.
# Get agent
Source: https://docs.blaxel.ai/api-reference/agents/get-agent
/api-reference/controlplane.yml get /agents/{agentName}
Returns detailed information about an agent including its current deployment status, configuration, events history, and inference endpoint URL.
# List all agent revisions
Source: https://docs.blaxel.ai/api-reference/agents/list-all-agent-revisions
/api-reference/controlplane.yml get /agents/{agentName}/revisions
# List all agents
Source: https://docs.blaxel.ai/api-reference/agents/list-all-agents
/api-reference/controlplane.yml get /agents
Returns AI agents deployed in the workspace. Each agent includes its deployment status, runtime configuration, and global inference endpoint URL. Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array with all agents.
# Update agent
Source: https://docs.blaxel.ai/api-reference/agents/update-agent
/api-reference/controlplane.yml put /agents/{agentName}
Updates an agent's configuration and triggers a new deployment. Changes to runtime settings, environment variables, or scaling parameters will be applied on the next deployment.
# Create application
Source: https://docs.blaxel.ai/api-reference/applications/create-application
/api-reference/controlplane.yml post /applications
Creates a new application deployment. Applications are long-running workloads that default to public access and mk3 generation.
# Delete application
Source: https://docs.blaxel.ai/api-reference/applications/delete-application
/api-reference/controlplane.yml delete /applications/{applicationName}
Permanently deletes an application and all its deployment history. The application endpoint will immediately stop responding. This action cannot be undone.
# Get application
Source: https://docs.blaxel.ai/api-reference/applications/get-application
/api-reference/controlplane.yml get /applications/{applicationName}
Returns detailed information about an application including its current deployment status, configuration, events history, and endpoint URL.
# List all application revisions
Source: https://docs.blaxel.ai/api-reference/applications/list-all-application-revisions
/api-reference/controlplane.yml get /applications/{applicationName}/revisions
# List all applications
Source: https://docs.blaxel.ai/api-reference/applications/list-all-applications
/api-reference/controlplane.yml get /applications
Returns applications deployed in the workspace. Each application includes its deployment status, runtime configuration, and endpoint URL. Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array with all applications.
# Update application
Source: https://docs.blaxel.ai/api-reference/applications/update-application
/api-reference/controlplane.yml put /applications/{applicationName}
Updates an application's configuration and triggers a new deployment. Changes to runtime settings, environment variables, or scaling parameters will be applied on the next deployment.
# Export the filesystem changes to a presigned URL
Source: https://docs.blaxel.ai/api-reference/archive/export-the-filesystem-changes-to-a-presigned-url
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml post /archive/export
Archives everything the sandbox changed on top of its base image and streams it, uncompressed, to a presigned S3 PUT URL. The memory of the sandbox is not archived.
The sandbox is quiesced first: the process list is saved (unless saveProcesses is false), every process is stopped, and the API then refuses the calls that would write to the filesystem. The freeze is not lifted afterwards, since an exported sandbox is meant to be restored elsewhere; call POST /archive/resume to lift it.
A sandbox still frozen by an earlier export is exported as it is, with the process list that export saved.
Use dryRun to get the archive's content and exact size without stopping anything and without uploading.
Set async to start the export and answer immediately, which is what archiving a large filesystem needs: the export then reports itself through GET /archive/status.
# Get the archive status
Source: https://docs.blaxel.ai/api-reference/archive/get-the-archive-status
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /archive/status
Reports whether the sandbox is frozen for an archive export, and which processes were stopped for it.
# Lift the archive freeze
Source: https://docs.blaxel.ai/api-reference/archive/lift-the-archive-freeze
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml post /archive/resume
Makes the API serve every route again after an export. The processes stopped for the export are not relaunched: this exists so a failed or aborted export does not leave the sandbox unusable.
# Billing API endpoint reference
Source: https://docs.blaxel.ai/api-reference/billing
HTTP endpoint reference for the Blaxel Billing Explorer API to programmatically retrieve cost, usage, and consumption metrics for your account.
## Endpoint
```http theme={null}
GET https://api.blaxel.ai/v0/accounts/{accountId}/metrics
```
The `{accountId}` path segment is the identifier of the Blaxel account that owns the workspaces you want to query. You can retrieve it from your account settings in the Blaxel Console, or by calling `GET /v0/accounts`.
All requests must be authenticated with a Bearer token. See [Access tokens](/Security/Access-tokens) for how to obtain one.
```bash theme={null}
curl "https://api.blaxel.ai/v0/accounts/{accountId}/metrics?startTime=2025-10-01T00:00:00Z&endTime=2025-11-01T00:00:00Z&groupBy=resource_type" \
-H "Authorization: Bearer $BL_API_KEY"
```
## Query parameters
### Required
| Parameter | Type | Description |
| - | - | - |
| `startTime` | `string` (RFC3339 UTC) | Start of the window, **inclusive**. Example: `2025-10-01T00:00:00Z`. |
| `endTime` | `string` (RFC3339 UTC) | End of the window, **exclusive**. Example: `2025-11-01T00:00:00Z`. |
### Optional
| Parameter | Type | Description |
| - | - | - |
| `resolution` | `string` | Bucket size for the returned timeseries. One of `hourly`, `daily`, `weekly`, `monthly`. Auto-picked from the window length when omitted. See [Resolution rules](#resolution-rules). |
| `groupBy` | `string` | Dimension(s) to split the response along. Accepts a single value or a comma-separated list of up to 3 dimensions (e.g. `groupBy=resource_uuid,workspace`). Valid dimensions: `workspace`, `resource_type`, `resource_name`, `resource_uuid`, `billing_dimension`. When multiple dimensions are provided, results are grouped by their cross-product. Dimensions are canonically sorted, so `resource_uuid,workspace` and `workspace,resource_uuid` yield the same result. When omitted the response contains a single aggregated entry. |
| `workspace` | `string` | Restrict to a single workspace name. **Admin-only.** |
| `resourceType` | `string` | Filter by raw `resource_type` (e.g. `sandbox`, `agent`, `job`, `mcp_server`, `volume`, `custom_domain`). |
| `resourceName` | `string` | Filter by resource name (primary identifier). |
| `resourceUuid` | `string` | Filter by resource UUID / instance id. Best-effort — not every resource type carries an instance id. |
| `billingDimension` | `string` | Filter by canonical billing dimension. See [Billing dimensions](#billing-dimensions). |
| `limit` | `integer` | Page size for grouped results. Default `100`, max `100`. Values above `100` are silently clamped. |
| `cursor` | `string` | Opaque pagination cursor returned in `meta.nextCursor` of a previous response. |
Use the [enum discovery endpoints](#enum-discovery-endpoints) to populate dropdowns for `groupBy` and `resourceType` values without hard-coding them.
## Resolution rules
When `resolution` is omitted, Blaxel picks one based on the window length:
| Window length | Default resolution |
| - | - |
| `< 7 days` | `hourly` |
| `< 90 days` | `daily` |
| `< 1 year` | `weekly` |
| `≥ 1 year` | `monthly` |
Explicit values have maximum window caps to keep responses bounded:
| Resolution | Max window |
| - | - |
| `hourly` | 7 days |
| `daily` | 90 days |
| `weekly` | 1 year |
| `monthly` | unbounded |
Requesting a resolution that exceeds its cap returns `400 Bad Request`.
## Admin-only parameters
The following two features are restricted to **account admins** — non-admin callers get `403 Access denied`:
* `workspace=` — filtering to a single workspace
* `groupBy=workspace` — splitting the response by workspace (including when `workspace` appears as one dimension in a multi-dimension `groupBy`, e.g. `groupBy=resource_type,workspace`)
Non-admin members can still query their own workspace's data via `workspace=` when they are admins of the **account**, which is distinct from the workspace admin role. See [workspace access control](/Security/Workspace-access-control) for more on the role model.
## Billing dimensions
A **billing dimension** is the canonical, unit-stable metric that Blaxel bills against. Each resource type maps to one or more dimensions, and each dimension carries a single unit (`gbs` = GB-seconds, `count` = discrete events, `hours` = active hours).
| Billing dimension | Resource type | Unit |
| - | - | - |
| `sandbox_compute_runtime_gbs` | sandbox | GB-seconds |
| `sandbox_image_storage_gbs` | sandbox | GB-seconds |
| `sandbox_snapshot_storage_gbs` | sandbox | GB-seconds |
| `agent_compute_runtime_gbs` | agent | GB-seconds |
| `agent_image_storage_gbs` | agent | GB-seconds |
| `agent_async_requests_count` | agent (async requests) | count |
| `job_compute_runtime_gbs` | job | GB-seconds |
| `job_image_storage_gbs` | job | GB-seconds |
| `mcp_server_compute_runtime_gbs` | mcp\_server | GB-seconds |
| `mcp_server_image_storage_gbs` | mcp\_server | GB-seconds |
| `volume_storage_gbs` | volume | GB-seconds |
| `custom_domain_active_time_hours` | custom\_domain | hours |
Because different dimensions use different units, **mixed-unit totals are deliberately omitted**. `summary.totalUsage` (and per-group `summary.usage`) is **only populated when `billing_dimension` is among the requested `groupBy` dimensions** — this guarantees every row shares a single unit. This applies to both `groupBy=billing_dimension` alone and multi-dimension queries that include it (e.g. `groupBy=billing_dimension,workspace`). For groupings that do not include `billing_dimension` you will only see `totalCost` / `cost` in USD — the unit-safe common denominator.
## Response
A successful call returns `200 OK` with an `AccountMetricsResponse`. Example for `groupBy=resource_type` — `usage` fields are omitted per the rule above:
```json theme={null}
{
"startTime": "2025-10-01T00:00:00Z",
"endTime": "2025-11-01T00:00:00Z",
"resolution": "daily",
"summary": {
"totalCost": 142.37
},
"data": [
{
"resourceType": "sandbox",
"summary": { "cost": 102.11 },
"timeseries": [
{ "timestamp": "2025-10-01T00:00:00Z", "cost": 3.41 },
{ "timestamp": "2025-10-02T00:00:00Z", "cost": 3.12 },
"..."
]
},
{
"resourceType": "agent",
"summary": { "cost": 28.90 },
"timeseries": ["..."]
}
],
"meta": {
"hasMore": false,
"nextCursor": ""
}
}
```
### Top-level fields
| Field | Description |
| - | - |
| `startTime` / `endTime` | Echo of the queried window, in RFC3339 UTC. |
| `resolution` | Bucket size actually applied (either what you asked for, or the auto-picked default). |
| `summary.totalCost` | Total cost in USD across the full window. Sum of every group's cost in `data`. |
| `summary.totalUsage` | Total usage across the window. **Only populated** when `billing_dimension` is among the requested `groupBy` dimensions, where every row shares a single unit. Omitted otherwise. |
| `data` | Array of grouped entries. When `groupBy` is not set, contains a single aggregated entry. |
| `meta.hasMore` / `meta.nextCursor` | See [Pagination](#pagination). |
### Per-group entry
Each item in `data` is an `AccountMetricsDataEntry`. The fields populated depend on which `groupBy` dimension(s) were requested:
| Field | Populated when `groupBy` includes |
| - | - |
| `workspace` | `workspace`, `resource_uuid` (enriched), `resource_name` (enriched) |
| `resourceType` | `resource_type`, `resource_uuid` (enriched), `resource_name` (enriched), `billing_dimension` (enriched) |
| `resourceName` | `resource_name`, `resource_uuid` (enriched) |
| `resourceUuid` | `resource_uuid` |
| `billingDimension` | `billing_dimension` |
When using multi-dimension `groupBy`, all fields from each requested dimension are populated. Additionally, **metadata enrichment** fills in contextual fields — for example, `groupBy=resource_uuid` also populates `workspace`, `resourceName`, and `resourceType` from the underlying data.
Every entry also carries:
* `summary.cost` — cost in USD for this group over the window.
* `summary.usage` — usage for this group. Only populated when `billing_dimension` is among the requested `groupBy` dimensions (same unit-safety rule as the top-level summary).
* `timeseries` — one entry per resolution bucket for the full window. Buckets with no activity have `cost: 0`. Bucket-level `usage` follows the same rule as group-level `usage`.
## Pagination
Grouped responses are paginated. To request the next page, pass the `nextCursor` value from the previous response back as the `cursor` query parameter:
```bash theme={null}
curl "https://api.blaxel.ai/v0/accounts/{accountId}/metrics?startTime=...&endTime=...&groupBy=resource_name&limit=100&cursor=$NEXT_CURSOR" \
-H "Authorization: Bearer $BL_API_KEY"
```
When `meta.hasMore` is `false`, `meta.nextCursor` is empty and there are no more pages.
`limit` defaults to `100` and is capped at `100`; values above the cap are silently clamped.
## Enum discovery endpoints
Two helper endpoints expose the valid values for `groupBy` and `resourceType` so you can render dropdowns without baking the list into your client.
### `GET /v0/accounts/{accountId}/metrics/enums/group-by`
Returns every valid value for the `groupBy` query parameter. The enum is static but exposed as an endpoint so it stays in sync automatically if new groupings are added.
```json theme={null}
{ "values": ["workspace", "resource_type", "resource_name", "resource_uuid", "billing_dimension"] }
```
### `GET /v0/accounts/{accountId}/metrics/enums/resource-types`
Returns the distinct `resource_type` values **actually observed for the account in the supplied window**. Unlike the `group-by` enum, this list is dynamic: a resource type only appears once there is at least one metric event for it in range.
Required query parameters: `startTime`, `endTime` (same format as the main endpoint).
```json theme={null}
{ "values": ["sandbox", "agent", "job", "mcp_server"] }
```
## Errors
| Status | Meaning |
| - | - |
| `400 Bad Request` | Missing/invalid parameter, unsupported feature (`label`, `unit`), a `resolution` that exceeds its window cap, more than 3 `groupBy` dimensions, or an invalid/duplicate dimension name. |
| `403 Access denied` | Non-admin caller used `workspace=` or `groupBy=workspace` (including as part of a multi-dimension groupBy). |
## Multi-dimension groupBy
You can group by multiple dimensions at once by passing a comma-separated list. For example, `groupBy=resource_type,workspace` returns the cross-product — one entry per unique (resource\_type, workspace) combination:
```json theme={null}
{
"startTime": "2025-10-01T00:00:00Z",
"endTime": "2025-11-01T00:00:00Z",
"resolution": "daily",
"summary": {
"totalCost": 142.37
},
"data": [
{
"resourceType": "sandbox",
"workspace": "production",
"summary": { "cost": 80.00 },
"timeseries": [
{ "timestamp": "2025-10-01T00:00:00Z", "cost": 2.67 },
"..."
]
},
{
"resourceType": "sandbox",
"workspace": "staging",
"summary": { "cost": 22.11 },
"timeseries": ["..."]
},
{
"resourceType": "agent",
"workspace": "production",
"summary": { "cost": 28.90 },
"timeseries": ["..."]
}
],
"meta": {
"hasMore": false,
"nextCursor": ""
}
}
```
**Constraints:**
* Maximum **3 dimensions** per query. Exceeding this returns `400 Bad Request`.
* Pagination works the same way — the cursor is opaque and handles the composite grouping internally.
* `summary.usage` follows the same unit-safety rule: only populated when `billing_dimension` is among the requested dimensions.
## Example: daily cost per resource type over 30 days
```bash theme={null}
curl -G "https://api.blaxel.ai/v0/accounts/$ACCOUNT_ID/metrics" \
--data-urlencode "startTime=2025-10-01T00:00:00Z" \
--data-urlencode "endTime=2025-11-01T00:00:00Z" \
--data-urlencode "resolution=daily" \
--data-urlencode "groupBy=resource_type" \
-H "Authorization: Bearer $BL_API_KEY"
```
## Example: hourly sandbox compute cost for the last 24 hours
```bash theme={null}
START=$(date -u -d '24 hours ago' +%Y-%m-%dT%H:00:00Z)
END=$(date -u +%Y-%m-%dT%H:00:00Z)
curl -G "https://api.blaxel.ai/v0/accounts/$ACCOUNT_ID/metrics" \
--data-urlencode "startTime=$START" \
--data-urlencode "endTime=$END" \
--data-urlencode "resolution=hourly" \
--data-urlencode "billingDimension=sandbox_compute_runtime_gbs" \
-H "Authorization: Bearer $BL_API_KEY"
```
## Example: per-workspace breakdown (account admin only)
```bash theme={null}
curl -G "https://api.blaxel.ai/v0/accounts/$ACCOUNT_ID/metrics" \
--data-urlencode "startTime=2025-10-01T00:00:00Z" \
--data-urlencode "endTime=2025-11-01T00:00:00Z" \
--data-urlencode "groupBy=workspace" \
-H "Authorization: Bearer $BL_API_KEY"
```
## Example: cost per resource type per workspace (multi-dimension, admin only)
```bash theme={null}
curl -G "https://api.blaxel.ai/v0/accounts/$ACCOUNT_ID/metrics" \
--data-urlencode "startTime=2025-10-01T00:00:00Z" \
--data-urlencode "endTime=2025-11-01T00:00:00Z" \
--data-urlencode "groupBy=resource_type,workspace" \
-H "Authorization: Bearer $BL_API_KEY"
```
# Get latest changelog entries
Source: https://docs.blaxel.ai/api-reference/changelog/get-latest-changelog-entries
/api-reference/controlplane.yml get /changelog
Returns the latest public changelog entries for the controlplane UI. The origin response is intentionally not cached in memory; CloudFront caches this endpoint according to the Cache-Control header.
# Code reranking/semantic search
Source: https://docs.blaxel.ai/api-reference/codegen/code-rerankingsemantic-search
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /codegen/reranking/{path}
Uses Relace's code reranking model to find the most relevant files for a given query. This is useful as a first pass in agentic exploration to narrow down the search space.
Based on: https://docs.relace.ai/docs/code-reranker/agent
Query Construction: The query can be a short question or a more detailed conversation with the user request included. For a first pass, use the full conversation; for subsequent calls, use more targeted questions.
Token Limit and Score Threshold: For 200k token context models like Claude 4 Sonnet, recommended defaults are scoreThreshold=0.5 and tokenLimit=30000.
The response will be a list of file paths and contents ordered from most relevant to least relevant.
# Archive sandbox
Source: https://docs.blaxel.ai/api-reference/compute/archive-sandbox
/api-reference/controlplane.yml post /sandboxes/{sandboxName}/archive
Archives a sandbox. The changes its filesystem accumulated over its image are exported to the archive store, then the sandbox is shut down while its definition is kept, so it can be recreated later with the same disk. Memory and running processes are not preserved, processes are started again from their configuration when the sandbox is unarchived.
# Create sandbox
Source: https://docs.blaxel.ai/api-reference/compute/create-sandbox
/api-reference/controlplane.yml post /sandboxes
Creates a new sandbox VM for secure AI code execution. Sandboxes automatically scale to zero when idle and resume instantly, preserving memory state including running processes and filesystem.
# Create Sandbox Preview
Source: https://docs.blaxel.ai/api-reference/compute/create-sandbox-preview
/api-reference/controlplane.yml post /sandboxes/{sandboxName}/previews
Create a preview
# Create Sandbox Schedule
Source: https://docs.blaxel.ai/api-reference/compute/create-sandbox-schedule
/api-reference/controlplane.yml post /sandboxes/{sandboxName}/schedules
Adds a schedule to a Sandbox.
# Create sandbox snapshot
Source: https://docs.blaxel.ai/api-reference/compute/create-sandbox-snapshot
/api-reference/controlplane.yml post /sandboxes/{sandboxName}/snapshots
Creates a point-in-time snapshot of a sandbox. Snapshots capture the sandbox state and can be used for forking into new sandboxes or applications. This is a WIP endpoint — the full implementation depends on the execution plane.
# Create snapshot
Source: https://docs.blaxel.ai/api-reference/compute/create-snapshot
/api-reference/controlplane.yml post /snapshots
Captures a snapshot from a source object. The snapshot belongs to the workspace rather than to its source, so it survives the deletion of the object it was captured from, and carries what creating a sandbox or an application from it needs.
# Create token for Sandbox Preview
Source: https://docs.blaxel.ai/api-reference/compute/create-token-for-sandbox-preview
/api-reference/controlplane.yml post /sandboxes/{sandboxName}/previews/{previewName}/tokens
Creates a token for a Sandbox Preview.
# Delete sandbox
Source: https://docs.blaxel.ai/api-reference/compute/delete-sandbox
/api-reference/controlplane.yml delete /sandboxes/{sandboxName}
Permanently deletes a sandbox and all its data. If no volumes are attached, this guarantees zero data retention (ZDR). This action cannot be undone.
# Delete Sandbox Preview
Source: https://docs.blaxel.ai/api-reference/compute/delete-sandbox-preview
/api-reference/controlplane.yml delete /sandboxes/{sandboxName}/previews/{previewName}
Deletes a Sandbox Preview by name.
# Delete Sandbox Schedule
Source: https://docs.blaxel.ai/api-reference/compute/delete-sandbox-schedule
/api-reference/controlplane.yml delete /sandboxes/{sandboxName}/schedules/{scheduleId}
Deletes a Sandbox Schedule by id.
# Delete sandbox snapshot
Source: https://docs.blaxel.ai/api-reference/compute/delete-sandbox-snapshot
/api-reference/controlplane.yml delete /sandboxes/{sandboxName}/snapshots/{snapshotId}
Deletes a snapshot of a sandbox, addressed by its name among that sandbox's snapshots (or by its ID).
# Delete snapshot
Source: https://docs.blaxel.ai/api-reference/compute/delete-snapshot
/api-reference/controlplane.yml delete /snapshots/{snapshotName}
Deletes a snapshot. There is a single snapshot object, so this removes it for the whole workspace, whether or not the object it was captured from still exists.
# Delete token for Sandbox Preview
Source: https://docs.blaxel.ai/api-reference/compute/delete-token-for-sandbox-preview
/api-reference/controlplane.yml delete /sandboxes/{sandboxName}/previews/{previewName}/tokens/{tokenName}
Deletes a token for a Sandbox Preview by name.
# Fork sandbox
Source: https://docs.blaxel.ai/api-reference/compute/fork-sandbox
/api-reference/controlplane.yml post /sandboxes/{sandboxName}/fork
Forks a sandbox into a new sandbox or application. When forking to a sandbox, the target must not already exist (409 if it does). When forking to an application, a new revision is added if the app already exists, or a new application is created. With no snapshotId, a fork to a sandbox copies the source sandbox's live state directly and no snapshot is persisted, while a fork to an application takes a snapshot its revision then references. This is a WIP endpoint — the full implementation depends on the execution plane.
# Fork snapshot
Source: https://docs.blaxel.ai/api-reference/compute/fork-snapshot
/api-reference/controlplane.yml post /snapshots/{snapshotName}/fork
Creates a new sandbox or application from a snapshot. The snapshot is enough on its own, so this works after the object it was captured from has been deleted.
# Get sandbox
Source: https://docs.blaxel.ai/api-reference/compute/get-sandbox
/api-reference/controlplane.yml get /sandboxes/{sandboxName}
Returns detailed information about a sandbox including its configuration, attached volumes, lifecycle policies, and API endpoint URL.
# Get sandbox by external ID
Source: https://docs.blaxel.ai/api-reference/compute/get-sandbox-by-external-id
/api-reference/controlplane.yml get /sandboxes/by-external-id/{externalId}
Returns the most recent non-terminated sandbox matching the given external ID. If no active sandbox is found, returns 404.
# Get Sandbox Preview
Source: https://docs.blaxel.ai/api-reference/compute/get-sandbox-preview
/api-reference/controlplane.yml get /sandboxes/{sandboxName}/previews/{previewName}
Returns a Sandbox Preview by name.
# Get Sandbox Schedule
Source: https://docs.blaxel.ai/api-reference/compute/get-sandbox-schedule
/api-reference/controlplane.yml get /sandboxes/{sandboxName}/schedules/{scheduleId}
Returns a Sandbox Schedule by id.
# Get Sandbox Scheduling Metrics
Source: https://docs.blaxel.ai/api-reference/compute/get-sandbox-scheduling-metrics
/api-reference/controlplane.yml get /schedules/metrics
Returns active sandbox and scheduling metrics for a UTC minute window. since is inclusive and until is exclusive. The default window is the last 24 hours and the maximum is 7 days. Execution status describes submission acceptance, not process completion. Execution totals begin accumulating when metrics collection is enabled and can lag recent firings briefly.
# Get snapshot
Source: https://docs.blaxel.ai/api-reference/compute/get-snapshot
/api-reference/controlplane.yml get /snapshots/{snapshotName}
Returns a snapshot of the workspace by ID.
# Get tokens for Sandbox Preview
Source: https://docs.blaxel.ai/api-reference/compute/get-tokens-for-sandbox-preview
/api-reference/controlplane.yml get /sandboxes/{sandboxName}/previews/{previewName}/tokens
Gets tokens for a Sandbox Preview.
# List Sandbox Previews
Source: https://docs.blaxel.ai/api-reference/compute/list-sandbox-previews
/api-reference/controlplane.yml get /sandboxes/{sandboxName}/previews
Returns a list of Sandbox Previews in the workspace.
# List Sandbox Schedule Executions
Source: https://docs.blaxel.ai/api-reference/compute/list-sandbox-schedule-executions
/api-reference/controlplane.yml get /sandboxes/{sandboxName}/schedule-executions
Returns the execution history of a Sandbox's schedules (across all schedules of the sandbox), newest first. Cursor-paginated via the `cursor` and `limit` query parameters. Each item records the HTTP status of submitting the scheduled command and the process name for log lookup.
# List Sandbox Schedules
Source: https://docs.blaxel.ai/api-reference/compute/list-sandbox-schedules
/api-reference/controlplane.yml get /sandboxes/{sandboxName}/schedules
Returns the schedules configured on a Sandbox. Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older versions return a bare array.
# List sandbox snapshots
Source: https://docs.blaxel.ai/api-reference/compute/list-sandbox-snapshots
/api-reference/controlplane.yml get /sandboxes/{sandboxName}/snapshots
Returns a list of snapshots for the specified sandbox.
# List sandboxes
Source: https://docs.blaxel.ai/api-reference/compute/list-sandboxes
/api-reference/controlplane.yml get /sandboxes
Returns sandboxes in the workspace. Each sandbox includes its configuration, status, and endpoint URL. Terminated sandboxes are hidden by default; pass `showTerminated=true` to include them. Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array of all sandboxes.
# List snapshots
Source: https://docs.blaxel.ai/api-reference/compute/list-snapshots
/api-reference/controlplane.yml get /snapshots
Returns the snapshots of the workspace, newest first by default, including the ones whose source object has been deleted. Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array.
# List Workspace Schedule Executions
Source: https://docs.blaxel.ai/api-reference/compute/list-workspace-schedule-executions
/api-reference/controlplane.yml get /schedule-executions
Returns schedule execution submissions across the workspace, newest first by default. Status describes submission acceptance, not process completion. since and until are inclusive RFC 3339 bounds on creation time.
# List Workspace Schedules
Source: https://docs.blaxel.ai/api-reference/compute/list-workspace-schedules
/api-reference/controlplane.yml get /schedules
Returns schedule definitions across the workspace, newest first by default.
# Restore sandbox from snapshot
Source: https://docs.blaxel.ai/api-reference/compute/restore-sandbox-from-snapshot
/api-reference/controlplane.yml post /sandboxes/{sandboxName}/snapshots/{snapshotId}/restore
Restores a sandbox to one of its own snapshots. The running sandbox is torn down and rebuilt from the snapshot under the same name and URLs, so everything it held since the snapshot was taken is lost unless it was itself snapshotted.
# Unarchive sandbox
Source: https://docs.blaxel.ai/api-reference/compute/unarchive-sandbox
/api-reference/controlplane.yml post /sandboxes/{sandboxName}/unarchive
Recreates an archived sandbox from its archive. The recreated sandbox runs the current generation whatever generation it was archived from, restores the archived filesystem before anything starts, and starts the saved processes again with new process identifiers.
# Update sandbox
Source: https://docs.blaxel.ai/api-reference/compute/update-sandbox
/api-reference/controlplane.yml put /sandboxes/{sandboxName}
Updates a sandbox's configuration. Note that certain changes (like image or memory) may reset the sandbox state. Use lifecycle policies to control automatic cleanup.
# Update Sandbox Preview
Source: https://docs.blaxel.ai/api-reference/compute/update-sandbox-preview
/api-reference/controlplane.yml put /sandboxes/{sandboxName}/previews/{previewName}
Updates a Sandbox Preview by name.
# Update Sandbox Schedule
Source: https://docs.blaxel.ai/api-reference/compute/update-sandbox-schedule
/api-reference/controlplane.yml put /sandboxes/{sandboxName}/schedules/{scheduleId}
Updates a Sandbox Schedule by id.
# Get platform configuration
Source: https://docs.blaxel.ai/api-reference/configurations/get-platform-configuration
/api-reference/controlplane.yml get /configuration
Returns global platform configuration including available regions, countries, continents, and private locations for deployment policies.
# Create application custom domain
Source: https://docs.blaxel.ai/api-reference/customdomains/create-application-custom-domain
/api-reference/controlplane.yml post /applications/{applicationName}/customdomains
Creates a new custom domain scoped to a specific application. After creation, you must configure DNS records and verify domain ownership before it becomes active.
# Create custom domain
Source: https://docs.blaxel.ai/api-reference/customdomains/create-custom-domain
/api-reference/controlplane.yml post /customdomains
Creates a new custom domain for preview deployments. After creation, you must configure DNS records and verify domain ownership before it becomes active.
# Delete custom domain
Source: https://docs.blaxel.ai/api-reference/customdomains/delete-custom-domain
/api-reference/controlplane.yml delete /customdomains/{domainName}
# Get custom domain
Source: https://docs.blaxel.ai/api-reference/customdomains/get-custom-domain
/api-reference/controlplane.yml get /customdomains/{domainName}
# List custom domain shares
Source: https://docs.blaxel.ai/api-reference/customdomains/list-custom-domain-shares
/api-reference/controlplane.yml get /customdomains/{domainName}/share
Returns the list of workspaces (and/or the account) that a custom domain is currently shared with.
# List custom domains
Source: https://docs.blaxel.ai/api-reference/customdomains/list-custom-domains
/api-reference/controlplane.yml get /customdomains
Returns all custom domains configured in the workspace. Custom domains allow serving preview deployments under your own domain (e.g., preview.yourdomain.com).
# Share a custom domain
Source: https://docs.blaxel.ai/api-reference/customdomains/share-a-custom-domain
/api-reference/controlplane.yml post /customdomains/{domainName}/share
Shares a verified custom domain with another workspace of the same account by copying the domain record; the ACM certificate and CloudFront tenant stay with the owner and are reused (not re-provisioned). Use targetWorkspace "account" to make the domain usable by every workspace of the account. Cross-account sharing is not supported.
# Unshare a custom domain
Source: https://docs.blaxel.ai/api-reference/customdomains/unshare-a-custom-domain
/api-reference/controlplane.yml delete /customdomains/{domainName}/share/{targetWorkspace}
Revokes sharing of a custom domain with a target workspace (or the account when targetWorkspace is "account"). Removes the shared copy / clears the account-global marker; the owner's domain is not affected.
# Update custom domain
Source: https://docs.blaxel.ai/api-reference/customdomains/update-custom-domain
/api-reference/controlplane.yml put /customdomains/{domainName}
# Verify custom domain
Source: https://docs.blaxel.ai/api-reference/customdomains/verify-custom-domain
/api-reference/controlplane.yml post /customdomains/{domainName}/verify
# Attach a drive to a local path
Source: https://docs.blaxel.ai/api-reference/drive/attach-a-drive-to-a-local-path
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml post /drives/mount
Mounts an agent drive using the blfs binary to a local path, optionally mounting a subpath within the drive. Supports optional UID/GID mapping to remap file ownership between the local sandbox and the filer (always mapped to filer UID/GID 0). Mapping values can be set per-request via uidMap/gidMap fields, or globally via BLFS_UID_MAP/BLFS_GID_MAP environment variables (request values take precedence).
# Detach a drive from a local path
Source: https://docs.blaxel.ai/api-reference/drive/detach-a-drive-from-a-local-path
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml delete /drives/mount/{mountPath}
Unmounts a previously mounted drive from the specified local path
# List currently mounted drives
Source: https://docs.blaxel.ai/api-reference/drive/list-currently-mounted-drives
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /drives/mount
Returns a list of all currently mounted drives managed by blfs
# Create a drive
Source: https://docs.blaxel.ai/api-reference/drives/create-a-drive
/api-reference/controlplane.yml post /drives
Creates a new drive in the workspace. Drives can be buckets and can be mounted at runtime to sandboxes.
# Create drive access token
Source: https://docs.blaxel.ai/api-reference/drives/create-drive-access-token
/api-reference/controlplane.yml post /drives/{driveName}/access-token
Issues a short-lived JWT access token scoped to a specific drive. The token can be used as Bearer authentication for direct S3 operations against the drive's bucket.
# Delete a drive
Source: https://docs.blaxel.ai/api-reference/drives/delete-a-drive
/api-reference/controlplane.yml delete /drives/{driveName}
Starts the deletion of a drive. The drive is marked DELETING and its storage is wiped asynchronously; the drive disappears from listings once the cleanup completes.
# Get a drive
Source: https://docs.blaxel.ai/api-reference/drives/get-a-drive
/api-reference/controlplane.yml get /drives/{driveName}
Retrieves details of a specific drive including its status and events.
# Get drive by external ID
Source: https://docs.blaxel.ai/api-reference/drives/get-drive-by-external-id
/api-reference/controlplane.yml get /drives/by-external-id/{externalId}
Returns a drive matching the given external ID. If no drive is found, returns 404.
# Get drive token JWKS
Source: https://docs.blaxel.ai/api-reference/drives/get-drive-token-jwks
/api-reference/controlplane.yml get /drives/jwks.json
Returns the JSON Web Key Set containing the Ed25519 public key used to verify drive access tokens. Other S3-compatible storage can use this endpoint to validate Bearer tokens.
# List drives
Source: https://docs.blaxel.ai/api-reference/drives/list-drives
/api-reference/controlplane.yml get /drives
Returns all drives in the workspace. Drives provide persistent storage that can be attached to agents, functions, and sandboxes. Starting with API version 2026-04-28, the response wraps items in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array with all drives.
# Update a drive
Source: https://docs.blaxel.ai/api-reference/drives/update-a-drive
/api-reference/controlplane.yml put /drives/{driveName}
Updates an existing drive. Metadata fields like displayName and labels can be changed. Size can be set if not already configured.
# Apply code edit
Source: https://docs.blaxel.ai/api-reference/fastapply/apply-code-edit
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml put /codegen/fastapply/{path}
Uses the configured LLM provider (Relace or Morph) to apply a code edit to the original content.
To use this endpoint as an agent tool, follow these guidelines:
Use this tool to make an edit to an existing file. This will be read by a less intelligent model, which will quickly apply the edit. You should make it clear what the edit is, while also minimizing the unchanged code you write.
When writing the edit, you should specify each edit in sequence, with the special comment "// ... existing code ..." to represent unchanged code in between edited lines.
Example format:
// ... existing code ...
FIRST_EDIT
// ... existing code ...
SECOND_EDIT
// ... existing code ...
THIRD_EDIT
// ... existing code ...
You should still bias towards repeating as few lines of the original file as possible to convey the change. But, each edit should contain minimally sufficient context of unchanged lines around the code you're editing to resolve ambiguity.
DO NOT omit spans of pre-existing code (or comments) without using the "// ... existing code ..." comment to indicate its absence. If you omit the existing code comment, the model may inadvertently delete these lines.
If you plan on deleting a section, you must provide context before and after to delete it. If the initial code is "Block 1\nBlock 2\nBlock 3", and you want to remove Block 2, you would output "// ... existing code ...\nBlock 1\nBlock 3\n// ... existing code ...".
Make sure it is clear what the edit should be, and where it should be applied. Make edits to a file in a single edit_file call instead of multiple edit_file calls to the same file. The apply model can handle many distinct edits at once.
# Retrieve feature flag evaluation for workspace
Source: https://docs.blaxel.ai/api-reference/featureflags/retrieve-feature-flag-evaluation-for-workspace
/api-reference/controlplane.yml get /features/{featureKey}
Evaluates a specific feature flag for the workspace with full details including variant and payload. Useful for testing and debugging feature flag targeting.
# Abort multipart upload
Source: https://docs.blaxel.ai/api-reference/filesystem/abort-multipart-upload
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml delete /filesystem-multipart/{uploadId}/abort
Abort a multipart upload and clean up all parts
# Complete multipart upload
Source: https://docs.blaxel.ai/api-reference/filesystem/complete-multipart-upload
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml post /filesystem-multipart/{uploadId}/complete
Complete a multipart upload by assembling all parts
# Create or update a file or directory
Source: https://docs.blaxel.ai/api-reference/filesystem/create-or-update-a-file-or-directory
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml put /filesystem/{path}
Create or update a file or directory.
Idempotent: an existing file is overwritten (truncated, not appended to) and an existing directory is kept, so retrying the same request is safe.
Send either a JSON body (FileRequest) or `multipart/form-data` for binary files. Multipart fields, in any order: `file` (required, the file content), `permissions` (optional octal mode such as `0755`, applied when the file is created, default `0644`; an existing file keeps its mode), `path` (optional, ignored: the target is always the URL path).
# Create or update directory tree
Source: https://docs.blaxel.ai/api-reference/filesystem/create-or-update-directory-tree
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml put /filesystem/tree/{path}
Create or update multiple files within a directory tree structure. Idempotent: existing files are overwritten, so retrying the same request is safe.
# Delete directory tree
Source: https://docs.blaxel.ai/api-reference/filesystem/delete-directory-tree
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml delete /filesystem/tree/{path}
Delete a directory tree recursively
# Delete file or directory
Source: https://docs.blaxel.ai/api-reference/filesystem/delete-file-or-directory
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml delete /filesystem/{path}
Delete a file or directory
# Find files and directories
Source: https://docs.blaxel.ai/api-reference/filesystem/find-files-and-directories
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /filesystem-find/{path}
Finds files and directories using the find command.
# Fuzzy search for files and directories
Source: https://docs.blaxel.ai/api-reference/filesystem/fuzzy-search-for-files-and-directories
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /filesystem-search/{path}
Ranks the files and directories under a path by how well their relative path fuzzy-matches `query` (fzf algorithm: the query's characters must appear in order, not necessarily next to each other), best match first.
Fuzzy search is for "jump to file" lookups from a partial name. The `patterns` parameter is currently ignored by this endpoint; use find for exact glob filtering.
# Get directory tree
Source: https://docs.blaxel.ai/api-reference/filesystem/get-directory-tree
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /filesystem/tree/{path}
Get a recursive directory tree structure starting from the specified path
# Get file or directory information
Source: https://docs.blaxel.ai/api-reference/filesystem/get-file-or-directory-information
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /filesystem/{path}
Get content of a file or listing of a directory. Use Accept header to control response format for files.
# Initiate multipart upload
Source: https://docs.blaxel.ai/api-reference/filesystem/initiate-multipart-upload
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml post /filesystem-multipart/initiate/{path}
Initiate a multipart upload session for a file
# List multipart uploads
Source: https://docs.blaxel.ai/api-reference/filesystem/list-multipart-uploads
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /filesystem-multipart
List all active multipart uploads
# List parts
Source: https://docs.blaxel.ai/api-reference/filesystem/list-parts
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /filesystem-multipart/{uploadId}/parts
List all uploaded parts for a multipart upload
# Search for text content in files
Source: https://docs.blaxel.ai/api-reference/filesystem/search-for-text-content-in-files
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /filesystem-content-search/{path}
Searches for text content inside files. Returns each matching line, with the lines around it when contextLines is set.
# Stat a file or directory
Source: https://docs.blaxel.ai/api-reference/filesystem/stat-a-file-or-directory
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml head /filesystem/{path}
Returns the metadata of a file or directory as headers, with no body. This checks stat availability, not permission to read file contents or list a directory. When the path does not exist or its metadata cannot be accessed, the response is an empty 200 without the X-File-Type header.
# Stream file modification events in a directory
Source: https://docs.blaxel.ai/api-reference/filesystem/stream-file-modification-events-in-a-directory
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /watch/filesystem/{path}
Streams change events for a directory until the client disconnects.
The body is JSON lines (sent with `Content-Type: text/plain`): one event object per line, e.g. `{"op":"WRITE","name":"main.go","path":"/app/src","error":null}`. `op` is the fsnotify operation (CREATE, WRITE, REMOVE, RENAME or CHMOD, several can be joined with `|`), `name` the base name of the changed entry, `path` the directory containing it, and `error` is always null. A `[keepalive]` line (not JSON) is sent every 30 seconds.
Only the directory's direct entries are watched. To also watch every subdirectory, including ones created later, end the path with `/**`. The stream reports subsequent changes without an initial snapshot. When a new directory is detected, synthetic CREATE events are emitted for entries already present inside it to cover changes before its watch was registered.
# Upload part
Source: https://docs.blaxel.ai/api-reference/filesystem/upload-part
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml put /filesystem-multipart/{uploadId}/part
Upload a single part of a multipart upload. Re-uploading a part number replaces that part. Wait for the previous request for that part to finish before retrying.
# Create MCP server
Source: https://docs.blaxel.ai/api-reference/functions/create-mcp-server
/api-reference/controlplane.yml post /functions
Creates a new MCP server function deployment. The function will expose tools via the Model Context Protocol that can be used by AI agents. Supports streamable HTTP transport.
# Delete MCP server
Source: https://docs.blaxel.ai/api-reference/functions/delete-mcp-server
/api-reference/controlplane.yml delete /functions/{functionName}
Permanently deletes an MCP server function and all its deployment history. Any agents using this function's tools will no longer be able to invoke them.
# Get MCP server
Source: https://docs.blaxel.ai/api-reference/functions/get-mcp-server
/api-reference/controlplane.yml get /functions/{functionName}
Returns detailed information about an MCP server function including its deployment status, available tools, transport configuration, and endpoint URL.
# List all MCP servers
Source: https://docs.blaxel.ai/api-reference/functions/list-all-mcp-servers
/api-reference/controlplane.yml get /functions
Returns MCP server functions deployed in the workspace. Each function includes its deployment status, transport protocol (websocket or http-stream), and endpoint URL. Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array with all functions.
# List function revisions
Source: https://docs.blaxel.ai/api-reference/functions/list-function-revisions
/api-reference/controlplane.yml get /functions/{functionName}/revisions
Returns revisions for a function by name.
# Update MCP server
Source: https://docs.blaxel.ai/api-reference/functions/update-mcp-server
/api-reference/controlplane.yml put /functions/{functionName}
Updates an MCP server function's configuration and triggers a new deployment. Changes to runtime settings, integrations, or transport protocol will be applied on the next deployment.
# Accept a pending image share
Source: https://docs.blaxel.ai/api-reference/images/accept-a-pending-image-share
/api-reference/controlplane.yml post /pending-image-shares/{pendingShareId}/accept
Accepts a pending cross-account image share and copies the image metadata to the target workspace. Caller must be an admin of the target workspace.
# Build a container image
Source: https://docs.blaxel.ai/api-reference/images/build-a-container-image
/api-reference/controlplane.yml post /images
Builds or imports a container image without creating a deployment. Provide a registry image reference to download and convert an existing image, or omit image to receive a presigned URL for uploading source code. Registry imports can specify memoryMb and volumeMb for the import worker. These settings do not change the resources of workloads using the image.
# Cleanup unused container images
Source: https://docs.blaxel.ai/api-reference/images/cleanup-unused-container-images
/api-reference/controlplane.yml delete /images
Cleans up unused container images in the workspace registry. Only removes images that are not currently referenced by any active agent, function, sandbox, or job deployment.
# Decline a pending image share
Source: https://docs.blaxel.ai/api-reference/images/decline-a-pending-image-share
/api-reference/controlplane.yml post /pending-image-shares/{pendingShareId}/decline
Declines a pending cross-account image share. Caller must be an admin of the target workspace.
# Delete container image
Source: https://docs.blaxel.ai/api-reference/images/delete-container-image
/api-reference/controlplane.yml delete /images/{resourceType}/{imageName}
Deletes a container image and all its tags from the workspace registry. Will fail if the image is currently in use by an active deployment.
# Delete container image tag
Source: https://docs.blaxel.ai/api-reference/images/delete-container-image-tag
/api-reference/controlplane.yml delete /images/{resourceType}/{imageName}/tags/{tagName}
Deletes a specific tag from a container image. The underlying image layers are kept if other tags reference them. Will fail if the tag is currently in use.
# Get container image
Source: https://docs.blaxel.ai/api-reference/images/get-container-image
/api-reference/controlplane.yml get /images/{resourceType}/{imageName}
Returns a bounded image summary starting with API version 2026-09-22. Older versions return the image with all tags.
# List container images
Source: https://docs.blaxel.ai/api-reference/images/list-container-images
/api-reference/controlplane.yml get /images
Returns paginated image summaries starting with API version 2026-09-22. Older versions return a bare array including all tags.
# List image shares
Source: https://docs.blaxel.ai/api-reference/images/list-image-shares
/api-reference/controlplane.yml get /images/{resourceType}/{imageName}/share
Returns the list of workspaces that a container image is currently shared with.
# List image tags
Source: https://docs.blaxel.ai/api-reference/images/list-image-tags
/api-reference/controlplane.yml get /images/{resourceType}/{imageName}/tags
Returns a bounded page of image tags. Search by prefix or exact name. Tags are ordered by name only. Send Blaxel-Version 2026-09-22 with image catalog requests.
# List pending image shares
Source: https://docs.blaxel.ai/api-reference/images/list-pending-image-shares
/api-reference/controlplane.yml get /pending-image-shares
Lists pending cross-account image shares targeting the caller's workspace (incoming) or originating from it (outgoing). Expired shares are cleaned up opportunistically.
# Share a container image
Source: https://docs.blaxel.ai/api-reference/images/share-a-container-image
/api-reference/controlplane.yml post /images/{resourceType}/{imageName}/share
Shares a container image with another workspace by copying the metadata record. The underlying storage (S3) data is not duplicated. For same-account targets the share is applied immediately. For cross-account targets, a pending image share is created and must be explicitly accepted by an admin of the target workspace; the correct target account ID must be supplied as an anti-spam measure.
# Unshare a container image
Source: https://docs.blaxel.ai/api-reference/images/unshare-a-container-image
/api-reference/controlplane.yml delete /images/{resourceType}/{imageName}/share/{targetWorkspace}
Revokes sharing of a container image with a target workspace. Removes the metadata copy from the target workspace. The source image is not affected.
# Inference API
Source: https://docs.blaxel.ai/api-reference/inference
HTTP reference for the inference endpoints automatically generated when you deploy agents, model APIs, and MCP servers on the Global Agentics Network.
Whenever you deploy a workload on Blaxel, an **inference endpoint** is generated on Global Agentics Network, the [infrastructure powerhouse](../Infrastructure/Global-Inference-Network) that hosts it.
The inference API URL depends on the type of workload ([sandbox](../Sandboxes/Overview), [agent](../Agents/Overview), [model API](../Models/Overview), [MCP server](../Functions/Overview)) you are interacting with:
```http Connect to a sandbox's MCP server theme={null}
https://sbx-{YOUR-SANDBOX}-{YOUR-WORKSPACE}.{REGION}.bl.run/mcp
```
```http Connect to a sandbox's REST API base URL theme={null}
https://sbx-{YOUR-SANDBOX}-{YOUR-WORKSPACE}.{REGION}.bl.run/
```
```http Query agent theme={null}
POST https://run.blaxel.ai/{YOUR-WORKSPACE}/agents/{YOUR-AGENT}
```
```http Query model API theme={null}
POST https://run.blaxel.ai/{YOUR-WORKSPACE}/models/{YOUR-MODEL}
```
```http Connect to an MCP server theme={null}
https://run.blaxel.ai/{YOUR-WORKSPACE}/functions/{YOUR-SERVER-NAME}/mcp
```
```http Execute a job theme={null}
POST https://api.blaxel.ai/{YOUR-WORKSPACE}/jobs/{YOUR-JOB-NAME}/executions
```
Showing the full request, with the input payload:
```http theme={null}
curl -X GET "https://sbx-{your-sandbox}-{your-workspace}.{region}.bl.run/filesystem/tree/etc" \
-H "Content-Type: application/json" \
-H "X-Blaxel-Authorization: Bearer "
```
If you have the Blaxel CLI `bl` installed, you can use it to directly interpolate your API key into the HTTP request, as shown below:
```http theme={null}
curl -X GET "https://sbx-{your-sandbox}-$(bl workspace --current).{region}.bl.run/filesystem/tree/etc" \
-H "Content-Type: application/json" \
-H "X-Blaxel-Authorization: Bearer $(bl token)"
```
`etc` is resolved relative to the filesystem service's working directory. To address the absolute path `/etc`, URL-encode the leading slash as `%2Fetc`. See [Address file paths over HTTP](/Sandboxes/Filesystem#address-file-paths-over-http) for details and multipart upload examples.
```http theme={null}
curl -X POST "https://run.blaxel.ai/{your-workspace}/agents/{your-agent}" \
-H "Content-Type: application/json" \
-H "X-Blaxel-Authorization: Bearer " \
-d '{"inputs":"Hello, world!"}'
```
If you have the Blaxel CLI `bl` installed, you can use it to directly interpolate your API key into the HTTP request, as shown below:
```http theme={null}
curl -X POST "https://run.blaxel.ai/$(bl workspace --current)/agents/{your-agent}" \
-H "Content-Type: application/json" \
-H "X-Blaxel-Authorization: Bearer $(bl token)" \
-d '{"inputs":"Hello, world!"}'
```
```http theme={null}
curl -X POST "https://run.blaxel.ai/{your-workspace}/models/{your-model}/chat/completions" \
-H "Content-Type: application/json" \
-H "X-Blaxel-Authorization: Bearer " \
-d '{"messages":[{"role":"user","content":"Hello!"}]}'
```
If you have the Blaxel CLI `bl` installed, you can use it to directly interpolate your API key into the HTTP request, as shown below:
```http theme={null}
curl -X POST "https://run.blaxel.ai/$(bl workspace --current)/models/{your-model}/chat/completions" \
-H "Content-Type: application/json" \
-H "X-Blaxel-Authorization: Bearer $(bl token)" \
-d '{"messages":[{"role":"user","content":"Hello!"}]}'
```
```http theme={null}
curl -X POST "https://api.blaxel.ai/{your-workspace}/jobs/{your-job-name}/executions" \
-H "X-Blaxel-Authorization: Bearer " \
-d '{"tasks":[{"my_arg":"my_value"}]}'
```
If you have the Blaxel CLI `bl` installed, you can use it to directly interpolate your API key into the HTTP request, as shown below:
```http theme={null}
curl -X POST "https://api.blaxel.ai/$(bl workspace --current)/jobs/{your-job-name}/executions" \
-H "X-Blaxel-Authorization: Bearer $(bl token)" \
-d '{"tasks":[{"my_arg":"my_value"}]}'
```
### Connect to MCP servers
**MCP servers** ([Model Context Protocol](https://github.com/modelcontextprotocol)) provide a toolkit of multiple capabilities for agents. These servers can be interacted with using Blaxel's streamable HTTP transport implementation on the server's global endpoint.
Learn how to run tool calls through your MCP server.
### Manage sessions
To simulate multi-turn conversations, you can pass on request headers. You'll need your client to generate this ID and pass it using any header which you can retrieve via the code (e.g. `Thread-Id`). Without a thread ID, the agent won't maintain or use any conversation memory when processing the request.
This is only available for agent requests.
```http Query agent with thread ID theme={null}
curl -X POST "https://run.blaxel.ai/{your-workspace}/agents/{your-agent}" \
-H 'Content-Type: application/json' \
-H "X-Blaxel-Authorization: Bearer " \
-H "X-Blaxel-Thread-Id: " \
-d '{"inputs":"Hello, world!"}'
```
Read our product guide on querying an agent.
# Create integration connection
Source: https://docs.blaxel.ai/api-reference/integrations/create-integration-connection
/api-reference/controlplane.yml post /integrations/connections
Creates a new integration connection with credentials for an external service. The connection can then be used by models, functions, and other resources to authenticate with the service.
# Delete integration connection
Source: https://docs.blaxel.ai/api-reference/integrations/delete-integration-connection
/api-reference/controlplane.yml delete /integrations/connections/{connectionName}
Permanently deletes an integration connection. Any resources using this connection will lose access to the external service.
# Get integration connection
Source: https://docs.blaxel.ai/api-reference/integrations/get-integration-connection
/api-reference/controlplane.yml get /integrations/connections/{connectionName}
Returns detailed information about an integration connection including its provider type, configuration (secrets are masked), and usage status.
# Get integration connection model
Source: https://docs.blaxel.ai/api-reference/integrations/get-integration-connection-model
/api-reference/controlplane.yml get /integrations/connections/{connectionName}/models/{modelId}
Returns a model for an integration connection by ID.
# Get integration connection model endpoint configurations
Source: https://docs.blaxel.ai/api-reference/integrations/get-integration-connection-model-endpoint-configurations
/api-reference/controlplane.yml get /integrations/connections/{connectionName}/endpointConfigurations
Returns a list of all endpoint configurations for a model.
# Get integration provider info
Source: https://docs.blaxel.ai/api-reference/integrations/get-integration-provider-info
/api-reference/controlplane.yml get /integrations/{integrationName}
Returns metadata about an integration provider including available endpoints, authentication methods, and supported models or features.
# List integration connection models
Source: https://docs.blaxel.ai/api-reference/integrations/list-integration-connection-models
/api-reference/controlplane.yml get /integrations/connections/{connectionName}/models
Returns a list of all models for an integration connection.
# List integration connections
Source: https://docs.blaxel.ai/api-reference/integrations/list-integration-connections
/api-reference/controlplane.yml get /integrations/connections
Returns all configured integration connections in the workspace. Each connection stores credentials and settings for an external service (LLM provider, API, database).
# Update integration connection
Source: https://docs.blaxel.ai/api-reference/integrations/update-integration-connection
/api-reference/controlplane.yml put /integrations/connections/{connectionName}
Updates an integration connection's configuration or credentials. Changes take effect immediately for all resources using this connection.
# REST API overview
Source: https://docs.blaxel.ai/api-reference/introduction
Authenticate and interact with all Blaxel resources using REST APIs, with support for API key and OAuth 2.0 authentication methods.
Blaxel APIs allow you to interact with all resources inside of and across your workspace(s).
## Get started
Authentication to the Blaxel APIs can either be done using [API keys](../Security/Access-tokens) created from the Blaxel console, or through a [classic OAuth 2.0 flow](../Security/Access-tokens).
**API keys** allow you to get started quickly. Simply [generate an API key](../Security/Access-tokens) for your user or service account and use the API key as a bearer token in place of the authorization headers `Authorization` or `X-Blaxel-Authorization` in any call to the Blaxel APIs.
For example, to list models:
```bash theme={null}
curl 'https://api.blaxel.ai/v0/models' \
-H 'accept: application/json, text/plain, */*' \
-H 'X-Blaxel-Authorization: Bearer YOUR-API-KEY'
```
To use **short-lived JWTs**, see [the guide on using an OAuth 2.0 flow](../Security/Access-tokens).
See the reference documentation below for more information:
Run inference requests on your deployments.
Programmatically retrieve cost and usage metrics.
Manage agents, functions, policies and much more.
## API Versioning
The Blaxel API uses header-based versioning to evolve response formats and behavior without breaking existing clients.
### Version Header
All requests should include the `Blaxel-Version` header:
```bash curl theme={null}
curl 'https://api.blaxel.ai/v0/agents' \
-H 'X-Blaxel-Authorization: Bearer YOUR-API-KEY' \
-H 'Blaxel-Version: 2026-04-16'
```
### Version Format
Versions follow a Stripe-style date-based format:
* **Stable**: `YYYY-MM-DD` (e.g., `2026-04-16`)
* **Preview**: `YYYY-MM-DD.preview` (e.g., `2026-04-16.preview`)
### Default Behavior
If you omit the `Blaxel-Version` header, the API defaults to the earliest stable version (`2026-04-16`). This ensures backward compatibility — existing clients work without changes.
### Pinning to a Specific Version
To guarantee consistent response shapes across API updates, pin your client to a specific version:
```bash theme={null}
curl -H 'Blaxel-Version: 2026-04-20' https://api.blaxel.ai/v0/agents
```
Once a new version is released, you can update your client at your own pace.
### Unknown Versions
If you send an unsupported version, the API returns `400 Bad Request`:
```json theme={null}
{
"error": "invalid_api_version",
"message": "Unknown API version \"2026-99-99\". Valid versions: 2026-04-16. See https://docs.blaxel.ai/api-reference/introduction#api-versioning for the list of supported versions."
}
```
Update your client to use a version from the table below.
### Version History
Version
Released
Status
Notes
2026-04-16
2026-04-16
Stable
Initial version
## Pagination
Pagination is currently only available for the Management API.
List operations on the Management API return paginated results. Each response includes a `data` array and a `meta` object with cursor information for fetching subsequent pages.
Pagination requires `Blaxel-Version: 2026-04-28` or later. Requests without this header use the earliest stable version, which returns a bare array without pagination metadata.
### Request parameters
| Parameter | Type | Description |
| - | - | - |
| `cursor` | `string` | Opaque cursor returned by a previous response's meta.nextCursor. Only valid for the same query (workspace + filters); the server rejects cursors bound to a different query or older than 24h. Omit on the first page. |
| `limit` | `integer` | Maximum number of items to return per page. Defaults to 50, maximum `200`. |
| `sort` | `string` | Sort order. Options: `createdAt:desc` (default), `createdAt:asc`, `name:asc`, `name:desc`. |
For accounts with multiple workspaces, specify the workspace as an additional request parameter, such as `?workspace=WORKSPACE_NAME&...`, or via an additional `X-Blaxel-Workspace: WORKSPACE_NAME` header in the request.
### Response metadata
Each paginated response includes a `meta` object:
| Field | Type | Description |
| - | - | - |
| `hasMore` | `boolean` | `true` when additional pages are available. |
| `nextCursor` | `string` | Opaque cursor to pass as `cursor` on the next request. Empty when there are no more pages. |
| `total` | `integer` | Total number of items in the workspace, ignoring filters. |
### Fetch pages manually
Request the first page:
```bash theme={null}
curl 'https://api.blaxel.ai/v0/agents?limit=50' \
-H 'X-Blaxel-Authorization: Bearer YOUR-API-KEY' \
-H 'Blaxel-Version: 2026-04-28'
```
The response includes `meta.nextCursor`. Pass it as `cursor` to fetch the next page:
```bash theme={null}
curl 'https://api.blaxel.ai/v0/agents?limit=50&cursor=NEXT_CURSOR' \
-H 'X-Blaxel-Authorization: Bearer YOUR-API-KEY' \
-H 'Blaxel-Version: 2026-04-28'
```
Continue fetching pages until `meta.hasMore` is `false`.
You can also paginate results using [built-in SDK helpers](/sdk-reference/introduction#pagination).
# Cancel job execution
Source: https://docs.blaxel.ai/api-reference/jobs/cancel-job-execution
/api-reference/controlplane.yml delete /jobs/{jobId}/executions/{executionId}
Cancels a running job execution. Tasks already in progress will complete, but no new tasks will be started. The execution status changes to 'cancelling' then 'cancelled'.
# Create batch job
Source: https://docs.blaxel.ai/api-reference/jobs/create-batch-job
/api-reference/controlplane.yml post /jobs
Creates a new batch job definition for parallel AI task processing. Jobs can be triggered via API or scheduled, and support configurable parallelism, timeouts, and retry logic.
# Create job execution
Source: https://docs.blaxel.ai/api-reference/jobs/create-job-execution
/api-reference/controlplane.yml post /jobs/{jobId}/executions
Triggers a new execution of the batch job. Each execution runs multiple tasks in parallel according to the job's configured concurrency. Tasks can be parameterized via the request body.
# Delete batch job
Source: https://docs.blaxel.ai/api-reference/jobs/delete-batch-job
/api-reference/controlplane.yml delete /jobs/{jobId}
Permanently deletes a batch job definition and cancels any running executions. Historical execution data will be retained for a limited time.
# Get batch job
Source: https://docs.blaxel.ai/api-reference/jobs/get-batch-job
/api-reference/controlplane.yml get /jobs/{jobId}
Returns detailed information about a batch job including its runtime configuration, execution history, and deployment status.
# Get job execution
Source: https://docs.blaxel.ai/api-reference/jobs/get-job-execution
/api-reference/controlplane.yml get /jobs/{jobId}/executions/{executionId}
Returns detailed information about a specific job execution including status, task statistics (success/failure/running counts), and timing information.
# List batch jobs
Source: https://docs.blaxel.ai/api-reference/jobs/list-batch-jobs
/api-reference/controlplane.yml get /jobs
Returns batch job definitions in the workspace. Each job can be triggered to run multiple parallel tasks with configurable concurrency and retry settings. Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array with all jobs.
# List execution tasks
Source: https://docs.blaxel.ai/api-reference/jobs/list-execution-tasks
/api-reference/controlplane.yml get /jobs/{jobId}/executions/{executionId}/tasks
Returns one cursor-paginated page of an execution's tasks. Tasks are derived from event history each request; only the in-memory slicing is paginated, the events scan still fetches the whole event log behind the scenes. Available starting with API version 2026-04-28.
# List job executions
Source: https://docs.blaxel.ai/api-reference/jobs/list-job-executions
/api-reference/controlplane.yml get /jobs/{jobId}/executions
Returns executions for a batch job. Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older versions keep the legacy offset/limit contract and return a bare array.
# List job revisions
Source: https://docs.blaxel.ai/api-reference/jobs/list-job-revisions
/api-reference/controlplane.yml get /jobs/{jobId}/revisions
Returns revisions for a job by name.
# Update batch job
Source: https://docs.blaxel.ai/api-reference/jobs/update-batch-job
/api-reference/controlplane.yml put /jobs/{jobId}
Updates a batch job's configuration. Changes affect new executions; running executions continue with their original configuration.
# List deployment regions
Source: https://docs.blaxel.ai/api-reference/locations/list-deployment-regions
/api-reference/controlplane.yml get /locations
Returns all deployment regions with their current availability status and supported hardware flavors. Use this to discover where resources can be deployed.
# List MCP Hub servers
Source: https://docs.blaxel.ai/api-reference/mcphub/list-mcp-hub-servers
/api-reference/controlplane.yml get /mcp/hub
Returns all pre-built MCP server definitions available in the Blaxel Hub. These can be deployed directly to your workspace with pre-configured tools and integrations.
# Create model endpoint
Source: https://docs.blaxel.ai/api-reference/models/create-model-endpoint
/api-reference/controlplane.yml post /models
Creates a new model gateway endpoint that proxies requests to an external LLM provider. Requires an integration connection with valid API credentials for the target provider.
# Delete model endpoint
Source: https://docs.blaxel.ai/api-reference/models/delete-model-endpoint
/api-reference/controlplane.yml delete /models/{modelName}
Permanently deletes a model gateway endpoint. Any agents or applications using this endpoint will need to be updated to use a different model.
# Get model endpoint
Source: https://docs.blaxel.ai/api-reference/models/get-model-endpoint
/api-reference/controlplane.yml get /models/{modelName}
Returns detailed information about a model gateway endpoint including its provider configuration, integration connection, and usage status.
# List model endpoints
Source: https://docs.blaxel.ai/api-reference/models/list-model-endpoints
/api-reference/controlplane.yml get /models
Returns model gateway endpoints configured in the workspace. Each model represents a proxy to an external LLM provider (OpenAI, Anthropic, etc.) with unified access control. Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array with all models.
# List model revisions
Source: https://docs.blaxel.ai/api-reference/models/list-model-revisions
/api-reference/controlplane.yml get /models/{modelName}/revisions
Returns revisions for a model by name.
# Update model endpoint
Source: https://docs.blaxel.ai/api-reference/models/update-model-endpoint
/api-reference/controlplane.yml put /models/{modelName}
Updates a model gateway endpoint's configuration. Changes to provider settings or integration connection take effect immediately.
# Disconnect tunnel
Source: https://docs.blaxel.ai/api-reference/network/disconnect-tunnel
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml delete /network/tunnel
Stop the network tunnel and restore the original network configuration. WARNING: After disconnecting, the sandbox will lose all outbound internet connectivity (no egress). Inbound connections to the sandbox will still work. Use PUT /network/tunnel/config to re-establish the tunnel.
# Get open ports for a process
Source: https://docs.blaxel.ai/api-reference/network/get-open-ports-for-a-process
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /network/process/{pid}/ports
Get a list of all open ports for a process
# Start monitoring ports for a process
Source: https://docs.blaxel.ai/api-reference/network/start-monitoring-ports-for-a-process
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml post /network/process/{pid}/monitor
Start monitoring for new ports opened by a process
# Stop monitoring ports for a process
Source: https://docs.blaxel.ai/api-reference/network/stop-monitoring-ports-for-a-process
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml delete /network/process/{pid}/monitor
Stop monitoring for new ports opened by a process
# Update tunnel configuration
Source: https://docs.blaxel.ai/api-reference/network/update-tunnel-configuration
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml put /network/tunnel/config
Apply a new tunnel configuration on the fly. The existing tunnel is torn down and a new one is established. This endpoint is write-only; there is no corresponding GET to read the config back.
# Create governance policy
Source: https://docs.blaxel.ai/api-reference/policies/create-governance-policy
/api-reference/controlplane.yml post /policies
Creates a new governance policy to control where and how resources are deployed. Policies can restrict deployment to specific regions, countries, or continents for compliance.
# Delete governance policy
Source: https://docs.blaxel.ai/api-reference/policies/delete-governance-policy
/api-reference/controlplane.yml delete /policies/{policyName}
Permanently deletes a governance policy. Resources using this policy will need to be updated to use a different policy.
# Get governance policy
Source: https://docs.blaxel.ai/api-reference/policies/get-governance-policy
/api-reference/controlplane.yml get /policies/{policyName}
Returns detailed information about a governance policy including its type (location, flavor, or maxToken), restrictions, and which resource types it applies to.
# List governance policies
Source: https://docs.blaxel.ai/api-reference/policies/list-governance-policies
/api-reference/controlplane.yml get /policies
Returns governance policies in the workspace. Policies control deployment locations, hardware flavors, and token limits for agents, functions, and models. Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array with all policies.
# List resources using a policy
Source: https://docs.blaxel.ai/api-reference/policies/list-resources-using-a-policy
/api-reference/controlplane.yml get /policies/{policyName}/usages
Returns the names of every resource (agent, function, model, sandbox, job) currently referencing the given policy. Replaces the client-side fan-out the policies UI used to do over the listings.
# Update governance policy
Source: https://docs.blaxel.ai/api-reference/policies/update-governance-policy
/api-reference/controlplane.yml put /policies/{policyName}
Updates a governance policy's restrictions. Changes take effect on the next deployment of resources using this policy.
# Close a process's stdin
Source: https://docs.blaxel.ai/api-reference/process/close-a-processs-stdin
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml delete /process/{identifier}/stdin
Send EOF to the process by closing its stdin pipe. Idempotent. For stdio protocols such as MCP this is the clean shutdown path.
# Execute a command
Source: https://docs.blaxel.ai/api-reference/process/execute-a-command
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml post /process
Execute a command and return process information.
Streaming: with `Accept: application/x-ndjson` (or `Accept: text/event-stream`, kept for compatibility) the response is NDJSON (`Content-Type: application/x-ndjson`), not SSE: one JSON object per line, `{"type": "...", "data": "..."}`.
`type` is `stdout` or `stderr` (`data` is a raw output chunk, sent as soon as the process writes it, newlines included; if the process finished before any chunk was streamed, its output is sent instead as one event per line, without the newline), `keepalive` (every 5 seconds, no data), `error` (`data` is the message, ends the stream) or `result` (last event, `data` is the ProcessResponse as a JSON string).
# Get process by identifier
Source: https://docs.blaxel.ai/api-reference/process/get-process-by-identifier
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /process/{identifier}
Get information about a process by its PID or name
# Get process logs
Source: https://docs.blaxel.ai/api-reference/process/get-process-logs
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /process/{identifier}/logs
Get the stdout and stderr output of a process
# Kill a process
Source: https://docs.blaxel.ai/api-reference/process/kill-a-process
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml delete /process/{identifier}/kill
Request forceful termination. Poll GET /process/{identifier} until terminal status confirms the managed process has exited.
# List all processes
Source: https://docs.blaxel.ai/api-reference/process/list-all-processes
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /process
Get a list of all running and completed processes
# Stop a process
Source: https://docs.blaxel.ai/api-reference/process/stop-a-process
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml delete /process/{identifier}
Request graceful termination. Poll GET /process/{identifier} until terminal status confirms the managed process has exited.
# Stream process logs in real time
Source: https://docs.blaxel.ai/api-reference/process/stream-process-logs-in-real-time
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /process/{identifier}/logs/stream
Streams the stdout and stderr output of a process in real time: the output so far, then live output as the process writes it. Closes when the process exits or the client disconnects.
By default, output is plain text with `stdout:` or `stderr:` prefixes at the start of each source stream's lines. Partial lines (e.g. prompts) are sent without waiting for a newline; their continuations have no new prefix. The other stream is not held back while a line is incomplete, and chronological order across stdout and stderr is not guaranteed.
This format does not provide unambiguous framing: output from the other stream, or a `[keepalive]` marker sent every 30 seconds, can appear inside an unfinished line. A later continuation can therefore lack a prefix identifying its source. Do not rely on this text stream to reconstruct stdout and stderr separately; request NDJSON for source-preserving streaming, or use GET /process/{identifier}/logs for separate output snapshots.
With `Accept: application/x-ndjson`, each line is a JSON object with `type` (`stdout`, `stderr`, `keepalive`, `restart`, `truncated`, or `error`) and optional `data`. Output records retain source identity and original bytes, including partial lines and newlines, for both retained history and live output. For `encoding: "base64"`, decode `data` before concatenating bytes per source; this occurs for binary data or a UTF-8 character split across chunks. Chunk boundaries are arbitrary and ordering is collection order, not a strict chronology across streams.
`keepalive` has no output data. A quiet active stream sends an initial keepalive after attachment so clients can receive the response before sending stdin; subsequent keepalives are sent every 30 seconds. `restart` carries a supervisor restart notice, not process output. `truncated` reports a retention or slow-reader gap and must not be appended to stdout/stderr. `error` reports a streaming failure. The connection ends after the final process exit, including automatic restarts; there is no `result` record. Older processes without structured history return HTTP 409 for NDJSON; their text stream remains available.
# Write to a process's stdin
Source: https://docs.blaxel.ai/api-reference/process/write-to-a-processs-stdin
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml post /process/{identifier}/stdin
Write the raw request body to the stdin of a process started with "stdin": true. Bytes are forwarded verbatim, so include the trailing newline your protocol expects. Sequential requests keep their order; each body is written atomically with respect to other writers. The pipe does not survive a sandbox-api restart: once it is gone this returns 409 and the process must be restarted.
# List public ips
Source: https://docs.blaxel.ai/api-reference/publicips:list/list-public-ips
/api-reference/controlplane.yml get /publicIps
Returns a list of all public ips used in Blaxel..
# List Sandbox Hub templates
Source: https://docs.blaxel.ai/api-reference/sandboxhub/list-sandbox-hub-templates
/api-reference/controlplane.yml get /sandbox/hub
Returns all pre-built sandbox templates available in the Blaxel Hub. These include popular development environments with pre-installed tools and frameworks.
# Create service account
Source: https://docs.blaxel.ai/api-reference/service_accounts/create-service-account
/api-reference/controlplane.yml post /service_accounts
Creates a new service account for machine-to-machine authentication. Returns client ID and secret (secret is only shown once at creation). Use these credentials for OAuth client_credentials flow.
# Create service account API key
Source: https://docs.blaxel.ai/api-reference/service_accounts/create-service-account-api-key
/api-reference/controlplane.yml post /service_accounts/{clientId}/api_keys
Creates a new long-lived API key for a service account. The full key value is only returned once at creation. API keys can have optional expiration dates.
# Delete service account
Source: https://docs.blaxel.ai/api-reference/service_accounts/delete-service-account
/api-reference/controlplane.yml delete /service_accounts/{clientId}
Permanently deletes a service account and invalidates all its credentials. Any systems using this service account will lose access immediately.
# List service account API keys
Source: https://docs.blaxel.ai/api-reference/service_accounts/list-service-account-api-keys
/api-reference/controlplane.yml get /service_accounts/{clientId}/api_keys
Returns all long-lived API keys created for a service account. API keys provide an alternative to OAuth for simpler authentication scenarios.
# List service accounts
Source: https://docs.blaxel.ai/api-reference/service_accounts/list-service-accounts
/api-reference/controlplane.yml get /service_accounts
Returns all service accounts in the workspace. Service accounts are machine identities for external systems to authenticate with Blaxel via OAuth or API keys.
# Revoke service account API key
Source: https://docs.blaxel.ai/api-reference/service_accounts/revoke-service-account-api-key
/api-reference/controlplane.yml delete /service_accounts/{clientId}/api_keys/{apiKeyId}
Revokes an API key for a service account. The key becomes invalid immediately and any requests using it will fail authentication.
# Update service account
Source: https://docs.blaxel.ai/api-reference/service_accounts/update-service-account
/api-reference/controlplane.yml put /service_accounts/{clientId}
Updates a service account's name or description. Credentials (client ID/secret) cannot be changed.
# Health check
Source: https://docs.blaxel.ai/api-reference/system/health-check
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml get /health
Returns health status and system information including upgrade count and binary details
Also includes last upgrade attempt status with detailed error information if available
# Reload environment from guest metadata
Source: https://docs.blaxel.ai/api-reference/system/reload-environment-from-guest-metadata
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml post /environment/reload
Re-reads /bl/metadata and applies its environment to the sandbox-api process, so this process and every process started afterwards see the current values. Called by the guest init after an environment update; safe to call manually.
# Upgrade the sandbox-api
Source: https://docs.blaxel.ai/api-reference/system/upgrade-the-sandbox-api
https://raw.githubusercontent.com/blaxel-ai/sandbox/refs/heads/main/sandbox-api/docs/openapi.yml post /upgrade
Triggers an upgrade of the sandbox-api process. Returns 200 immediately before upgrading.
The upgrade will: download the specified binary from GitHub releases, validate it, and restart.
All running processes will be preserved across the upgrade.
Available versions: "latest" (default, most recent release), "develop", "main", or specific tag like "v1.0.0"
You can also specify a custom baseUrl for forks (defaults to https://github.com/blaxel-ai/sandbox/releases)
# Get template
Source: https://docs.blaxel.ai/api-reference/templates/get-template
/api-reference/controlplane.yml get /templates/{templateName}
Returns detailed information about a deployment template including its configuration, source code reference, and available parameters.
# List deployment templates
Source: https://docs.blaxel.ai/api-reference/templates/list-deployment-templates
/api-reference/controlplane.yml get /templates
Returns all deployment templates available for creating agents, functions, and other resources with pre-configured settings and code.
# Create persistent volume
Source: https://docs.blaxel.ai/api-reference/volumes/create-persistent-volume
/api-reference/controlplane.yml post /volumes
Creates a new persistent storage volume that can be attached to sandboxes. Volumes must be created in a specific region and can only attach to sandboxes in the same region.
# Delete persistent volume
Source: https://docs.blaxel.ai/api-reference/volumes/delete-persistent-volume
/api-reference/controlplane.yml delete /volumes/{volumeName}
Permanently deletes a volume and all its data. The volume must not be attached to any sandbox. This action cannot be undone.
# Get persistent volume
Source: https://docs.blaxel.ai/api-reference/volumes/get-persistent-volume
/api-reference/controlplane.yml get /volumes/{volumeName}
Returns detailed information about a volume including its size, region, attachment status, and any events history.
# Get volume by external ID
Source: https://docs.blaxel.ai/api-reference/volumes/get-volume-by-external-id
/api-reference/controlplane.yml get /volumes/by-external-id/{externalId}
Returns an active volume matching the given external ID. If no active volume is found, returns 404.
# List persistent volumes
Source: https://docs.blaxel.ai/api-reference/volumes/list-persistent-volumes
/api-reference/controlplane.yml get /volumes
Returns persistent storage volumes in the workspace. Volumes can be attached to sandboxes for durable file storage that persists across sessions and sandbox deletions. Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array of volumes.
# Update volume
Source: https://docs.blaxel.ai/api-reference/volumes/update-volume
/api-reference/controlplane.yml put /volumes/{volumeName}
Updates a volume.
# Create or update volume template
Source: https://docs.blaxel.ai/api-reference/volumetemplates/create-or-update-volume-template
/api-reference/controlplane.yml put /volume_templates/{volumeTemplateName}
Creates or updates a volume template.
# Create volume template
Source: https://docs.blaxel.ai/api-reference/volumetemplates/create-volume-template
/api-reference/controlplane.yml post /volume_templates
Creates a new volume template for initializing volumes with pre-configured filesystem contents. Optionally returns a presigned URL for uploading the template archive.
# Delete volume template
Source: https://docs.blaxel.ai/api-reference/volumetemplates/delete-volume-template
/api-reference/controlplane.yml delete /volume_templates/{volumeTemplateName}
Deletes a volume template by name.
# Delete volume template version
Source: https://docs.blaxel.ai/api-reference/volumetemplates/delete-volume-template-version
/api-reference/controlplane.yml delete /volume_templates/{volumeTemplateName}/versions/{versionName}
Deletes a specific version of a volume template.
# Get volume template
Source: https://docs.blaxel.ai/api-reference/volumetemplates/get-volume-template
/api-reference/controlplane.yml get /volume_templates/{volumeTemplateName}
Returns a volume template by name.
# List volume templates
Source: https://docs.blaxel.ai/api-reference/volumetemplates/list-volume-templates
/api-reference/controlplane.yml get /volume_templates
Returns all volume templates in the workspace. Volume templates contain pre-configured filesystem snapshots that can be used to initialize new volumes.
# Allocate a new egress IP from the gateway
Source: https://docs.blaxel.ai/api-reference/vpcs/allocate-a-new-egress-ip-from-the-gateway
/api-reference/controlplane.yml post /vpcs/{vpcName}/egressgateways/{gatewayName}/ips
# Create a VPC for the workspace
Source: https://docs.blaxel.ai/api-reference/vpcs/create-a-vpc-for-the-workspace
/api-reference/controlplane.yml post /vpcs
# Create an egress gateway in a VPC
Source: https://docs.blaxel.ai/api-reference/vpcs/create-an-egress-gateway-in-a-vpc
/api-reference/controlplane.yml post /vpcs/{vpcName}/egressgateways
# Delete a VPC
Source: https://docs.blaxel.ai/api-reference/vpcs/delete-a-vpc
/api-reference/controlplane.yml delete /vpcs/{vpcName}
# Delete an egress gateway
Source: https://docs.blaxel.ai/api-reference/vpcs/delete-an-egress-gateway
/api-reference/controlplane.yml delete /vpcs/{vpcName}/egressgateways/{gatewayName}
# Delete an egress IP
Source: https://docs.blaxel.ai/api-reference/vpcs/delete-an-egress-ip
/api-reference/controlplane.yml delete /vpcs/{vpcName}/egressgateways/{gatewayName}/ips/{ipName}
# Egress gateway sandbox attachments
Source: https://docs.blaxel.ai/api-reference/vpcs/egress-gateway-sandbox-attachments
/api-reference/controlplane.yml get /egressgateways/usage
Returns the inverse map (gateway → sandbox names) for the workspace. Used by the egress-IPs UI to render attachment counts without fetching the sandboxes listing full client-side.
# Get a VPC by name
Source: https://docs.blaxel.ai/api-reference/vpcs/get-a-vpc-by-name
/api-reference/controlplane.yml get /vpcs/{vpcName}
# Get an egress gateway by name
Source: https://docs.blaxel.ai/api-reference/vpcs/get-an-egress-gateway-by-name
/api-reference/controlplane.yml get /vpcs/{vpcName}/egressgateways/{gatewayName}
# Get an egress IP by name
Source: https://docs.blaxel.ai/api-reference/vpcs/get-an-egress-ip-by-name
/api-reference/controlplane.yml get /vpcs/{vpcName}/egressgateways/{gatewayName}/ips/{ipName}
# List all egress gateways across all VPCs in the workspace
Source: https://docs.blaxel.ai/api-reference/vpcs/list-all-egress-gateways-across-all-vpcs-in-the-workspace
/api-reference/controlplane.yml get /egressgateways
# List all egress IPs across all VPCs and gateways in the workspace
Source: https://docs.blaxel.ai/api-reference/vpcs/list-all-egress-ips-across-all-vpcs-and-gateways-in-the-workspace
/api-reference/controlplane.yml get /egressips
# List all VPCs in the workspace
Source: https://docs.blaxel.ai/api-reference/vpcs/list-all-vpcs-in-the-workspace
/api-reference/controlplane.yml get /vpcs
# List egress gateways in a VPC
Source: https://docs.blaxel.ai/api-reference/vpcs/list-egress-gateways-in-a-vpc
/api-reference/controlplane.yml get /vpcs/{vpcName}/egressgateways
# List egress IPs in a gateway
Source: https://docs.blaxel.ai/api-reference/vpcs/list-egress-ips-in-a-gateway
/api-reference/controlplane.yml get /vpcs/{vpcName}/egressgateways/{gatewayName}/ips
# Check workspace availability
Source: https://docs.blaxel.ai/api-reference/workspaces/check-workspace-availability
/api-reference/controlplane.yml post /workspaces/availability
Check if a workspace is available.
# Create workspace tenant
Source: https://docs.blaxel.ai/api-reference/workspaces/create-workspace-tenant
/api-reference/controlplane.yml post /workspaces
Creates a new workspace tenant. The authenticated user becomes the workspace admin. Requires a linked billing account.
# Delete workspace tenant
Source: https://docs.blaxel.ai/api-reference/workspaces/delete-workspace-tenant
/api-reference/controlplane.yml delete /workspaces/{workspaceName}
Permanently deletes a workspace and ALL its resources (agents, functions, sandboxes, volumes, etc.). This action cannot be undone. Only workspace admins can delete a workspace.
# Get enabled features for workspace
Source: https://docs.blaxel.ai/api-reference/workspaces/get-enabled-features-for-workspace
/api-reference/controlplane.yml get /features
Returns only the feature flags that are currently enabled for the specified workspace. Disabled features are not included to prevent information leakage.
# Get workspace details
Source: https://docs.blaxel.ai/api-reference/workspaces/get-workspace-details
/api-reference/controlplane.yml get /workspaces/{workspaceName}
Returns detailed information about a workspace including its display name, account ID, status, and runtime configuration.
# Invite user to workspace
Source: https://docs.blaxel.ai/api-reference/workspaces/invite-user-to-workspace
/api-reference/controlplane.yml post /users
Invites a new team member to the workspace by email. The invitee will receive an email to accept the invitation before gaining access to workspace resources.
# Leave workspace
Source: https://docs.blaxel.ai/api-reference/workspaces/leave-workspace
/api-reference/controlplane.yml delete /workspaces/{workspaceName}/leave
Leaves a workspace.
# List accessible workspaces
Source: https://docs.blaxel.ai/api-reference/workspaces/list-accessible-workspaces
/api-reference/controlplane.yml get /workspaces
Returns all workspaces the authenticated user has access to. Each workspace is a separate tenant with its own resources, team members, and billing.
# List workspace team members
Source: https://docs.blaxel.ai/api-reference/workspaces/list-workspace-team-members
/api-reference/controlplane.yml get /users
Returns all team members in the workspace including their roles (admin, member or viewer) and invitation status.
# Remove user from workspace or revoke invitation
Source: https://docs.blaxel.ai/api-reference/workspaces/remove-user-from-workspace-or-revoke-invitation
/api-reference/controlplane.yml delete /users/{subOrEmail}
Removes a user from the workspace (or revokes an invitation if the user has not accepted the invitation yet).
# Update user role in workspace
Source: https://docs.blaxel.ai/api-reference/workspaces/update-user-role-in-workspace
/api-reference/controlplane.yml put /users/{subOrEmail}
Updates the role of a user in the workspace.
# Update workspace
Source: https://docs.blaxel.ai/api-reference/workspaces/update-workspace
/api-reference/controlplane.yml put /workspaces/{workspaceName}
Updates a workspace's settings such as display name and labels. The workspace name cannot be changed after creation.
# bl apply
Source: https://docs.blaxel.ai/cli-reference/commands/bl_apply
## bl apply
Apply a configuration to a resource by file
### Synopsis
Apply configuration changes to resources declaratively using YAML files.
This command is similar to Kubernetes 'kubectl apply' - it creates resources
if they don't exist, or updates them if they do (idempotent operation).
Use 'apply' for Infrastructure as Code workflows where you:
* Manage resources via configuration files
* Version control your infrastructure
* Deploy multiple related resources together
* Implement GitOps practices
Difference from 'deploy':
* 'apply' manages resource configuration (metadata, settings, specs)
* 'deploy' builds and uploads code as container images
For deploying code changes to agents/jobs, use 'bl deploy'.
For managing resource configuration, use 'bl apply'.
The command respects environment variables and secrets, which can be injected
via -e flag for .env files or -s flag for command-line secrets.
```
bl apply [flags]
```
### Examples
```
# Apply a single resource
bl apply -f agent.yaml
# Apply all resources in directory
bl apply -f ./resources/ -R
# Apply with environment variable substitution
bl apply -f deployment.yaml -e .env.production
# Apply from stdin (useful for CI/CD)
cat config.yaml | bl apply -f -
# Apply with secrets
bl apply -f config.yaml -s API_KEY=xxx -s DB_PASSWORD=yyy
# Example YAML structure for an agent:
# apiVersion: blaxel.ai/v1alpha1
# kind: Agent
# metadata:
# name: my-agent
# spec:
# runtime:
# image: agent/my-template-agent:latest
# memory: 4096
# Create a sandbox with the default base image
bl apply -f - < \~/.local/share/bash-completion/completions/bl
# macOS:
bl completion bash > \$(brew --prefix)/etc/bash\_completion.d/bl
Zsh:
eval "\$(bl completion zsh)"
# To load completions for each session, execute once:
mkdir -p \~/.zsh/completions
bl completion zsh > \~/.zsh/completions/\_bl
Fish:
bl completion fish | source
# To load completions for each session, execute once:
bl completion fish > \~/.config/fish/completions/bl.fish
PowerShell:
bl completion powershell | Out-String | Invoke-Expression
# To load completions for each session, execute once:
bl completion powershell > bl.ps1
# and source this file from your PowerShell profile.
```
bl completion [bash|zsh|fish|powershell]
```
### Options
```
-h, --help help for completion
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl connect
Source: https://docs.blaxel.ai/cli-reference/commands/bl_connect
## bl connect
Open an interactive terminal session to a sandbox
### Synopsis
Open an interactive terminal session to a sandbox
### Options
```
-h, --help help for connect
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
* [bl connect sandbox](/cli-reference/commands/bl_connect_sandbox) - Connect to a sandbox environment
# bl connect sandbox
Source: https://docs.blaxel.ai/cli-reference/commands/bl_connect_sandbox
## bl connect sandbox
Connect to a sandbox environment
### Synopsis
Connect to a sandbox environment with an interactive terminal session.
This command opens a direct terminal connection to your sandbox, similar to SSH.
The terminal supports full ANSI colors, cursor movement, and interactive applications.
Press Ctrl+D to disconnect from the sandbox.
Examples:
bl connect sandbox my-sandbox
bl connect sb my-sandbox
bl connect sbx production-env
```
bl connect sandbox [sandbox-name] [flags]
```
### Options
```
-h, --help help for sandbox
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl connect](/cli-reference/commands/bl_connect) - Open an interactive terminal session to a sandbox
# bl delete
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete
## bl delete
Delete resources from your workspace
### Synopsis
Delete Blaxel resources from your workspace.
WARNING: Deletion is permanent and cannot be undone. Resources are immediately
deactivated and removed along with their configurations.
Two deletion modes:
1. By name: Use subcommands like 'bl delete agent my-agent'
2. By file: Use 'bl delete -f resource.yaml' for declarative management
What Happens:
* Resource is immediately stopped and deactivated
* Configuration and metadata are removed
* Associated logs and metrics may be retained (check workspace policy)
* Data volumes are NOT automatically deleted (use 'bl delete volume')
Before Deleting:
* Backup any important configuration or data
* Check dependencies (other resources using this one)
* Consider stopping instead of deleting for temporary disablement
Note: Deleting an agent/job stops it immediately but may not delete associated
storage volumes. Use 'bl get volumes' to see persistent storage and delete
separately if needed.
```
bl delete [flags]
```
### Examples
```
# Delete by name (using subcommands)
bl delete agent my-agent
bl delete job my-job
bl delete sandbox my-sandbox
# Delete multiple resources by name
bl delete volume vol1 vol2 vol3
bl delete agent agent1 agent2
# Delete a snapshot (removed from the workspace, source sandbox untouched)
bl delete snapshot 6f1c2e3a-8b4d-4c2e-9f0a-1d2e3f4a5b6c
# Delete a sandbox preview
bl delete sandbox my-sandbox preview my-preview
# Delete a sandbox preview token
bl delete sandbox my-sandbox preview my-preview token my-token
# Delete from YAML file
bl delete -f my-resource.yaml
# Delete multiple resources from directory
bl delete -f ./resources/ -R
# Delete from stdin (useful in pipelines)
cat resource.yaml | bl delete -f -
# Safe deletion workflow
bl get agent my-agent # Review resource first
bl delete agent my-agent # Delete after confirmation
# --- Bulk deletion with jq filtering ---
# WARNING: Bulk deletions are irreversible. Always preview first!
# STEP 1: Preview what would be deleted (ALWAYS DO THIS FIRST)
bl get jobs -o json | jq -r '.[] | select(.status == "DELETING") | .metadata.name'
# STEP 2: After verifying the list, proceed with deletion
bl delete jobs $(bl get jobs -o json | jq -r '.[] | select(.status == "DELETING") | .metadata.name')
# More bulk deletion examples (always preview first):
bl delete sandboxes $(bl get sandboxes -o json | jq -r '.[] | select(.status == "FAILED") | .metadata.name')
bl delete agents $(bl get agents -o json | jq -r '.[] | select(.metadata.name | contains("test")) | .metadata.name')
bl delete volumes $(bl get volumes -o json | jq -r '.[] | select(.metadata.labels.environment == "dev") | .metadata.name')
bl delete sandboxes $(bl get sandboxes -o json | jq -r '.[] | select(.metadata.name | test("^temp-")) | .metadata.name')
```
### Options
```
-f, --filename string containing the resource to delete.
-h, --help help for delete
-R, --recursive Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
* [bl delete agent](/cli-reference/commands/bl_delete_agent) - Delete one or more agents
* [bl delete application](/cli-reference/commands/bl_delete_application) - Delete one or more applications
* [bl delete drive](/cli-reference/commands/bl_delete_drive) - Delete one or more drives
* [bl delete function](/cli-reference/commands/bl_delete_function) - Delete one or more functions
* [bl delete image](/cli-reference/commands/bl_delete_image) - Delete images or image tags
* [bl delete integrationconnection](/cli-reference/commands/bl_delete_integrationconnection) - Delete one or more integrationconnections
* [bl delete job](/cli-reference/commands/bl_delete_job) - Delete one or more jobs
* [bl delete model](/cli-reference/commands/bl_delete_model) - Delete one or more models
* [bl delete policy](/cli-reference/commands/bl_delete_policy) - Delete one or more policies
* [bl delete preview](/cli-reference/commands/bl_delete_preview) - Delete one or more previews
* [bl delete previewtoken](/cli-reference/commands/bl_delete_previewtoken) - Delete one or more previewtokens
* [bl delete sandbox](/cli-reference/commands/bl_delete_sandbox) - Delete one or more sandboxes
* [bl delete snapshot](/cli-reference/commands/bl_delete_snapshot) - Delete one or more snapshots
* [bl delete volume](/cli-reference/commands/bl_delete_volume) - Delete one or more volumes
* [bl delete volumetemplate](/cli-reference/commands/bl_delete_volumetemplate) - Delete one or more volumetemplates
# bl delete agent
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_agent
## bl delete agent
Delete one or more agents
```
bl delete agent name [name...] [flags]
```
### Options
```
-h, --help help for agent
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl delete application
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_application
## bl delete application
Delete one or more applications
```
bl delete application name [name...] [flags]
```
### Options
```
-h, --help help for application
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl delete drive
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_drive
## bl delete drive
Delete one or more drives
```
bl delete drive name [name...] [flags]
```
### Options
```
-h, --help help for drive
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl delete function
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_function
## bl delete function
Delete one or more functions
```
bl delete function name [name...] [flags]
```
### Options
```
-h, --help help for function
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl delete image
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_image
## bl delete image
Delete images or image tags
### Synopsis
Delete container images or specific tags.
Usage patterns:
bl delete image agent/my-image Delete image with all its tags
bl delete image agent/my-image:v1.0 Delete only the specified tag
The image reference format is: resourceType/imageName\[:tag]
* resourceType: Type of resource (e.g., agent, function, job)
* imageName: The name of the image
* tag: Optional tag to delete only that specific version
WARNING: Deleting an image without specifying a tag will remove ALL tags.
```
bl delete image [resourceType/]imageName[:tag] ... [flags]
```
### Examples
```
# Delete an entire image (all tags)
bl delete image agent/my-agent
# Delete only a specific tag
bl delete image agent/my-agent:v1.0
# Delete multiple images/tags
bl delete image agent/img1:v1 agent/img2:v2
```
### Options
```
-h, --help help for image
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl delete integrationconnection
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_integrationconnection
## bl delete integrationconnection
Delete one or more integrationconnections
```
bl delete integrationconnection name [name...] [flags]
```
### Options
```
-h, --help help for integrationconnection
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl delete job
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_job
## bl delete job
Delete one or more jobs
```
bl delete job name [name...] [flags]
```
### Options
```
-h, --help help for job
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl delete model
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_model
## bl delete model
Delete one or more models
```
bl delete model name [name...] [flags]
```
### Options
```
-h, --help help for model
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl delete policy
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_policy
## bl delete policy
Delete one or more policies
```
bl delete policy name [name...] [flags]
```
### Options
```
-h, --help help for policy
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl delete preview
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_preview
## bl delete preview
Delete one or more previews
```
bl delete preview name [name...] [flags]
```
### Options
```
-h, --help help for preview
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl delete previewtoken
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_previewtoken
## bl delete previewtoken
Delete one or more previewtokens
```
bl delete previewtoken name [name...] [flags]
```
### Options
```
-h, --help help for previewtoken
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl delete sandbox
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_sandbox
## bl delete sandbox
Delete one or more sandboxes
```
bl delete sandbox name [name...] [flags]
```
### Options
```
-h, --help help for sandbox
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl delete snapshot
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_snapshot
## bl delete snapshot
Delete one or more snapshots
```
bl delete snapshot name [name...] [flags]
```
### Options
```
-h, --help help for snapshot
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl delete volume
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_volume
## bl delete volume
Delete one or more volumes
```
bl delete volume name [name...] [flags]
```
### Options
```
-h, --help help for volume
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl delete volumetemplate
Source: https://docs.blaxel.ai/cli-reference/commands/bl_delete_volumetemplate
## bl delete volumetemplate
Delete one or more volumetemplates
```
bl delete volumetemplate name [name...] [flags]
```
### Options
```
-h, --help help for volumetemplate
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl delete](/cli-reference/commands/bl_delete) - Delete resources from your workspace
# bl deploy
Source: https://docs.blaxel.ai/cli-reference/commands/bl_deploy
## bl deploy
Build, push, and deploy your project to Blaxel
### Synopsis
Deploy your Blaxel project to the cloud.
This command packages your code, builds a container image, and deploys it
to your workspace. The deployment process includes:
1. Reading configuration from blaxel.toml
2. Packaging source code (respects .blaxelignore)
3. Building container image with your runtime and dependencies
4. Uploading to Blaxel's container registry
5. Creating or updating the resource in your workspace
6. Streaming build and deployment logs (interactive mode)
A blaxel.toml configuration file is required. By default, the command looks
for it in the current directory. Use -d to specify a subdirectory containing
the blaxel.toml (useful for monorepo setups).
If the blaxel.toml contains an 'image' field pointing to a registry image,
the platform will pull the image and transform it via metamorph before deploying.
For private registries, supply credentials via --registry-cred or --docker-config.
Interactive vs Non-Interactive:
* Interactive (default): Shows live logs and deployment progress with TUI
* Non-interactive (--yes or CI): Runs without interactive UI, suitable for automation.
Returns once the code is submitted: exit 0 means accepted, not deployed.
* Non-interactive with --wait: Waits for this build and rollout (up to --timeout)
and exits 1 with the cause, failing Dockerfile step, a short log tail and a next
command if it fails or times out. Interactive mode always waits.
With --wait and -o json (or yaml), a failure adds resources\[].diagnostics (code,
phase, cause, step, source, logTail, next). An unchanged redeploy creates no new revision:
\--wait reports that in resources\[].note and exits 0. If no build starts within 3
minutes of the upload, --wait stops with DEPLOY\_TIMEOUT instead of waiting for --timeout.
Environment Variables and Secrets:
Use -e to load .env files or -s to pass secrets directly via command line.
Secrets are injected into your container at runtime and never stored in images.
Monorepo Support:
Use -d to deploy a specific subdirectory, or -R to recursively deploy
all projects in a monorepo (looks for blaxel.toml in subdirectories).
```
bl deploy [flags]
```
### Examples
```
# Basic deployment (interactive mode with live logs)
bl deploy
# Non-interactive deployment (for CI/CD); returns once submitted
bl deploy --yes
# Non-interactive deployment that waits for build and rollout and explains failures
bl deploy --yes --wait --timeout 20m
# Deploy with environment variables
bl deploy -e .env.production
# Deploy with command-line secrets
bl deploy -s API_KEY=xxx -s DB_PASSWORD=yyy
# Deploy without rebuilding (reuse existing image)
bl deploy --skip-build
# Dry run to validate configuration
bl deploy --dryrun
# Deploy specific subdirectory in monorepo
bl deploy -d ./packages/my-agent
# Deploy specifying a resource type
bl deploy --type sandbox
# Deploy with Docker build args from a .env.build file
bl deploy --build-env-file .env.build.production
# Recursively deploy all projects in monorepo
bl deploy -R
```
### Options
```
--build-env-file string Path to a build env file with Docker build args (default: auto-detect .env.build)
-d, --directory string Deployment app path, can be a sub directory
--docker-config string Path to a Docker config.json file with registry credentials
--dryrun Dry run the deployment
-e, --env-file strings Environment file to load (default [.env])
--experimental Enable experimental features (e.g. USER directive support)
-h, --help help for deploy
-n, --name string Optional name for the deployment
-r, --recursive Deploy recursively (default true)
-c, --registry-cred stringArray Registry credentials (format: registry=username:password, repeatable)
-s, --secrets strings Secrets to deploy
--skip-build Skip the build step
--timeout string Timeout for build and deployment monitoring (e.g. 30m, 1h). Defaults to 1h
-t, --type string Resource type (sandbox, agent, function, job, application). Defaults to blaxel.toml type or 'sandbox'
--wait In non-interactive mode, wait for build and rollout, exit 1 on failure and explain it (see --timeout)
-y, --yes Skip interactive mode
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl drive
Source: https://docs.blaxel.ai/cli-reference/commands/bl_drive
## bl drive
Manage drives and drive mounts on sandboxes
### Synopsis
Manage drives and drive mounts on sandboxes.
```
Drive CRUD:
bl drive list List all drives in the workspace
bl drive get Get details of a specific drive
bl drive create Create a new drive
bl drive delete Delete a drive
Sandbox mount operations:
bl drive mount --sandbox ... Mount a drive to a running sandbox
bl drive unmount --sandbox ... Unmount a drive from a running sandbox
bl drive mounts --sandbox List drives mounted in a running sandbox
```
### Examples
```
# List all drives
bl drive list
# Get details of a drive
bl drive get my-drive
# Create a new drive
bl drive create --name my-drive --region us-pdx-1
# Delete a drive
bl drive delete my-drive
# Mount a drive to a sandbox
bl drive mount --sandbox my-sandbox --drive my-drive --mount-path /mnt/data
# Unmount a drive from a sandbox
bl drive unmount --sandbox my-sandbox --mount-path /mnt/data
# List mounted drives in a sandbox
bl drive mounts --sandbox my-sandbox
```
### Options
```
-h, --help help for drive
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
* [bl drive create](/cli-reference/commands/bl_drive_create) - Create a new drive
* [bl drive delete](/cli-reference/commands/bl_drive_delete) - Delete a drive
* [bl drive get](/cli-reference/commands/bl_drive_get) - Get details of a specific drive
* [bl drive list](/cli-reference/commands/bl_drive_list) - List all drives in the workspace
* [bl drive mount](/cli-reference/commands/bl_drive_mount) - Mount a drive to a sandbox
* [bl drive mounts](/cli-reference/commands/bl_drive_mounts) - List mounted drives in a sandbox
* [bl drive unmount](/cli-reference/commands/bl_drive_unmount) - Unmount a drive from a sandbox
# bl drive create
Source: https://docs.blaxel.ai/cli-reference/commands/bl_drive_create
## bl drive create
Create a new drive
### Synopsis
Create a new drive in the current workspace.
```
bl drive create [flags]
```
### Examples
```
# Create a drive in a specific region
bl drive create --name my-drive --region us-pdx-1
```
### Options
```
-h, --help help for create
--name string Name of the drive
--region string Deployment region (e.g., us-pdx-1, eu-lon-1)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl drive](/cli-reference/commands/bl_drive) - Manage drives and drive mounts on sandboxes
# bl drive delete
Source: https://docs.blaxel.ai/cli-reference/commands/bl_drive_delete
## bl drive delete
Delete a drive
### Synopsis
Delete a drive from the current workspace.
```
bl drive delete [flags]
```
### Examples
```
# Delete a drive
bl drive delete my-drive
```
### Options
```
-h, --help help for delete
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl drive](/cli-reference/commands/bl_drive) - Manage drives and drive mounts on sandboxes
# bl drive get
Source: https://docs.blaxel.ai/cli-reference/commands/bl_drive_get
## bl drive get
Get details of a specific drive
### Synopsis
Get detailed information about a drive in the current workspace.
```
bl drive get [flags]
```
### Examples
```
# Get drive details
bl drive get my-drive
# Get drive details in JSON format
bl drive get my-drive -o json
```
### Options
```
-h, --help help for get
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl drive](/cli-reference/commands/bl_drive) - Manage drives and drive mounts on sandboxes
# bl drive list
Source: https://docs.blaxel.ai/cli-reference/commands/bl_drive_list
## bl drive list
List all drives in the workspace
### Synopsis
List all drives in the current workspace.
```
bl drive list [flags]
```
### Examples
```
# List all drives
bl drive list
# List drives in JSON format
bl drive list -o json
# Fetch the next page
bl drive list --cursor
# Fetch every drive
bl drive list --all
```
### Options
```
--all Fetch all pages (may be slow for large collections)
--cursor string Cursor from a previous page to fetch the next page of results
-h, --help help for list
--limit int Maximum number of items to return (auto-paginates when above 200) (default 200)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl drive](/cli-reference/commands/bl_drive) - Manage drives and drive mounts on sandboxes
# bl drive mount
Source: https://docs.blaxel.ai/cli-reference/commands/bl_drive_mount
## bl drive mount
Mount a drive to a sandbox
### Synopsis
Mount or re-mount a drive to a sandbox environment.
This command attaches an agent drive to a local path inside the sandbox using
the blfs filesystem. It can be used as a recovery tool when mounts are lost.
```
bl drive mount [flags]
```
### Examples
```
# Mount a drive with default settings
bl drive mount --sandbox my-sandbox --drive my-drive --mount-path /mnt/data
# Mount a subdirectory of the drive
bl drive mount --sandbox my-sandbox --drive my-drive --mount-path /mnt/data --drive-path /subdir
# Mount as read-only
bl drive mount --sandbox my-sandbox --drive my-drive --mount-path /mnt/data --read-only
# Mount with UID/GID mapping
bl drive mount --sandbox my-sandbox --drive my-drive --mount-path /mnt/data --uid-map 1000 --gid-map 1000
```
### Options
```
--drive string Name of the drive to mount
--drive-path string Subdirectory within the drive to mount (optional, defaults to /)
--gid-map string Local GID to map (filer GID is always 0)
-h, --help help for mount
--mount-path string Local path inside the sandbox to mount the drive
--read-only Mount the drive as read-only
--sandbox string Name of the sandbox
--uid-map string Local UID to map (filer UID is always 0)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl drive](/cli-reference/commands/bl_drive) - Manage drives and drive mounts on sandboxes
# bl drive mounts
Source: https://docs.blaxel.ai/cli-reference/commands/bl_drive_mounts
## bl drive mounts
List mounted drives in a sandbox
### Synopsis
List all currently mounted drives in a sandbox environment.
```
bl drive mounts [flags]
```
### Examples
```
# List all mounted drives
bl drive mounts --sandbox my-sandbox
```
### Options
```
-h, --help help for mounts
--sandbox string Name of the sandbox
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl drive](/cli-reference/commands/bl_drive) - Manage drives and drive mounts on sandboxes
# bl drive unmount
Source: https://docs.blaxel.ai/cli-reference/commands/bl_drive_unmount
## bl drive unmount
Unmount a drive from a sandbox
### Synopsis
Unmount a previously mounted drive from the specified local path inside a sandbox.
```
bl drive unmount [flags]
```
### Examples
```
# Unmount a drive
bl drive unmount --sandbox my-sandbox --mount-path /mnt/data
```
### Options
```
-h, --help help for unmount
--mount-path string Mount path to detach (must start with /)
--sandbox string Name of the sandbox
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl drive](/cli-reference/commands/bl_drive) - Manage drives and drive mounts on sandboxes
# bl fork
Source: https://docs.blaxel.ai/cli-reference/commands/bl_fork
## bl fork
Fork a sandbox into a new sandbox or application
### Synopsis
Create a new sandbox or application by forking an existing sandbox.
Forking into a sandbox copies the source's live state — its filesystem,
running processes and memory — straight into the new sandbox. No snapshot is
created, and none is left behind on the source. Forking a running source
pauses it for the moment its memory is captured.
Pass --snapshot-id to fork from a snapshot you took earlier instead of from
the source's live state. Forking into an application always goes through a
snapshot, which the application's revision references so you can re-activate
that revision later.
A fork inherits the source's image, memory and environment; only its
environment can be changed, and only through the API.
Arguments use the type/name format:
sbx/name or sandbox/name — sandbox resource
app/name or application/name — application resource
If the source has no type prefix, it defaults to sandbox.
```
bl fork [flags]
```
### Examples
```
# Fork a sandbox into a new sandbox, copying its live state
bl fork sbx/my-sandbox sbx/my-sandbox-fork
# Fork from a snapshot taken earlier instead of the live state
bl fork sbx/my-sandbox sbx/my-sandbox-fork --snapshot-id snap_abc123
# Fork a sandbox into an application
bl fork sbx/my-sandbox app/my-app
# Fork with canary traffic and port
bl fork sbx/my-sandbox app/my-app --traffic 20 --port 8080
# Short form (source defaults to sandbox)
bl fork my-sandbox app/my-app
```
### Options
```
-h, --help help for fork
--memory int Deprecated: a fork always inherits the source sandbox's memory
--port int Port to expose
--snapshot-id string Fork from this snapshot instead of the source's live state
--traffic int Canary traffic percentage for the new revision
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl get
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get
## bl get
List or retrieve Blaxel resources in your workspace
### Synopsis
Retrieve information about Blaxel resources in your workspace.
A "resource" in Blaxel refers to any deployable or manageable entity:
* agents: AI agent applications
* functions/mcp: Model Context Protocol servers (tool providers)
* jobs: Batch processing tasks
* sandboxes: Isolated execution environments
* snapshots: Point-in-time captures a sandbox or application can be created from
* applications: Application deployments with revision-based traffic management
* models: AI model configurations
* policies: Access control policies
* volumes: Persistent storage
* integrationconnections: External service integrations
Hub Discovery (pre-built resources available in the Blaxel Hub):
* sandbox-hub: Pre-built sandbox images with pre-installed tools and runtimes
* mcp-hub: Pre-built MCP servers for tool integrations (GitHub, Slack, etc.)
* templates: Project scaffolding templates for bl new
Output Formats:
Use -o flag to control output format:
* pretty: Human-readable colored output (default)
* json: Machine-readable JSON (for scripting)
* yaml: YAML format
* table: Tabular format with columns
Watch Mode:
Use --watch to continuously monitor a resource and see updates in real-time.
Useful for tracking deployment status or watching for changes.
The command can list all resources of a type or get details for a specific one.
### Examples
```
# List all agents
bl get agents
# Get specific agent details
bl get agent my-agent
# Get in JSON format (useful for scripting)
bl get agent my-agent -o json
# Watch agent status in real-time
bl get agent my-agent --watch
# List all resources with table output
bl get agents -o table
# Get MCP servers (also called functions)
bl get functions
bl get mcp
# List jobs
bl get jobs
# Get specific job
bl get job my-job
# List executions for a job (nested resource)
bl get job my-job executions
# Get specific execution for a job
bl get job my-job execution EXECUTION_ID
# List pre-built sandbox images from the Hub
bl get sandbox-hub
bl get sandbox-hub -o json
# List pre-built MCP servers from the Hub
bl get mcp-hub
bl get mcp-hub -o json
# List snapshots in the workspace
bl get snapshots
# Get a specific snapshot by id (from bl get snapshots)
bl get snapshot 6f1c2e3a-8b4d-4c2e-9f0a-1d2e3f4a5b6c
# Monitor sandbox status
bl get sandbox my-sandbox --watch
# List processes in a sandbox
bl get sandbox my-sandbox process
bl get sbx my-sandbox ps
# Get specific process in a sandbox
bl get sandbox my-sandbox process my-process
# List previews for a sandbox
bl get sandbox my-sandbox previews
# Get a specific preview
bl get sandbox my-sandbox preview my-preview
# List tokens for a sandbox preview
bl get sandbox my-sandbox preview my-preview tokens
# Get a specific token
bl get sandbox my-sandbox preview my-preview token my-token
# --- Filtering with jq ---
# Get names of all jobs with status DELETING
bl get jobs -o json | jq -r '.[] | select(.status == "DELETING") | .metadata.name'
# Get names of all deployed sandboxes
bl get sandboxes -o json | jq -r '.[] | select(.status == "DEPLOYED") | .metadata.name'
# Get all agents with name containing "test"
bl get agents -o json | jq -r '.[] | select(.metadata.name | contains("test")) | .metadata.name'
# Get sandboxes with specific label (e.g., environment=dev)
bl get sandboxes -o json | jq -r '.[] | select(.metadata.labels.environment == "dev") | .metadata.name'
# Get all job names
bl get jobs -o json | jq -r '.[] | .metadata.name'
# Count resources by status
bl get agents -o json | jq 'group_by(.status) | map({status: .[0].status, count: length})'
```
### Options
```
-h, --help help for get
--watch After listing/getting the requested object, watch for changes.
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
* [bl get agents](/cli-reference/commands/bl_get_agents) - List all agents or get details of a specific one
* [bl get applications](/cli-reference/commands/bl_get_applications) - List all applications or get details of a specific one
* [bl get drives](/cli-reference/commands/bl_get_drives) - List all drives or get details of a specific one
* [bl get functions](/cli-reference/commands/bl_get_functions) - List all functions or get details of a specific one
* [bl get image](/cli-reference/commands/bl_get_image) - List image summaries or image tags with cursor pagination
* [bl get integrationconnections](/cli-reference/commands/bl_get_integrationconnections) - List all integrationconnections or get details of a specific one
* [bl get jobs](/cli-reference/commands/bl_get_jobs) - List all jobs or get details of a specific one
* [bl get mcp-hub](/cli-reference/commands/bl_get_mcp-hub) - List pre-built MCP servers available in the Blaxel Hub
* [bl get models](/cli-reference/commands/bl_get_models) - List all models or get details of a specific one
* [bl get policies](/cli-reference/commands/bl_get_policies) - List all policies or get details of a specific one
* [bl get previews](/cli-reference/commands/bl_get_previews) - List all previews or get details of a specific one
* [bl get previewtokens](/cli-reference/commands/bl_get_previewtokens) - List all previewtokens or get details of a specific one
* [bl get sandbox-hub](/cli-reference/commands/bl_get_sandbox-hub) - List pre-built sandbox images available in the Blaxel Hub
* [bl get sandboxes](/cli-reference/commands/bl_get_sandboxes) - List all sandboxes or get details of a specific one
* [bl get snapshots](/cli-reference/commands/bl_get_snapshots) - List all snapshots or get details of a specific one
* [bl get templates](/cli-reference/commands/bl_get_templates) - List available project templates
* [bl get volumes](/cli-reference/commands/bl_get_volumes) - List all volumes or get details of a specific one
* [bl get volumetemplates](/cli-reference/commands/bl_get_volumetemplates) - List all volumetemplates or get details of a specific one
# bl get agents
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_agents
## bl get agents
List all agents or get details of a specific one
```
bl get agents [flags]
```
### Options
```
--all Fetch all pages (may be slow for large collections)
--cursor string Cursor from a previous page to fetch the next page of results
-h, --help help for agents
--limit int Maximum number of items to return (auto-paginates when above 200) (default 200)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get applications
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_applications
## bl get applications
List all applications or get details of a specific one
```
bl get applications [flags]
```
### Options
```
--all Fetch all pages (may be slow for large collections)
--cursor string Cursor from a previous page to fetch the next page of results
-h, --help help for applications
--limit int Maximum number of items to return (auto-paginates when above 200) (default 200)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get drives
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_drives
## bl get drives
List all drives or get details of a specific one
```
bl get drives [flags]
```
### Options
```
--all Fetch all pages (may be slow for large collections)
--cursor string Cursor from a previous page to fetch the next page of results
-h, --help help for drives
--limit int Maximum number of items to return (auto-paginates when above 200) (default 200)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get functions
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_functions
## bl get functions
List all functions or get details of a specific one
```
bl get functions [flags]
```
### Options
```
--all Fetch all pages (may be slow for large collections)
--cursor string Cursor from a previous page to fetch the next page of results
-h, --help help for functions
--limit int Maximum number of items to return (auto-paginates when above 200) (default 200)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get image
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_image
## bl get image
List image summaries or image tags with cursor pagination
### Synopsis
List one page of image repository summaries, or one page of tags for a named image.
Use --cursor to continue a listing and --all to fetch every page. Empty pages
may still have a next cursor. Repository summaries retain total size, tag count,
status and last deployment time without downloading their tags.
Search uses a case-sensitive name prefix. Search results are ordered by name
ascending. Tag pages support name:asc and name:desc only.
\--latest inspects every tag page and prints the most recently created tag reference.
```
bl get image [resourceType/imageName[:tag]] [flags]
```
### Examples
```
bl get images --limit 100
bl get images --q base --sort name:asc
bl get image sandbox/base --limit 20
bl get image sandbox/base --cursor CURSOR
bl get image sandbox/base:v1
bl get image sandbox/base --all
bl get image sandbox/base --latest
```
### Options
```
--all Fetch all pages instead of a single page
--cursor string Cursor from the previous page; keep the same search and sort
-h, --help help for image
--latest Return the most recent tag by creation time (reads all tag pages)
--limit int Maximum items per page (1-100) (default 100)
--q string Filter by a case-sensitive image or tag name prefix
--sort string Sort: name:asc, name:desc, createdAt:asc or createdAt:desc (tags: name only)
--source-workspace string Owner workspace of a shared image (named images only)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get integrationconnections
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_integrationconnections
## bl get integrationconnections
List all integrationconnections or get details of a specific one
```
bl get integrationconnections [flags]
```
### Options
```
-h, --help help for integrationconnections
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get jobs
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_jobs
## bl get jobs
List all jobs or get details of a specific one
```
bl get jobs [flags]
```
### Options
```
--all Fetch all pages (may be slow for large collections)
--cursor string Cursor from a previous page to fetch the next page of results
-h, --help help for jobs
--limit int Maximum number of items to return (auto-paginates when above 200) (default 200)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get mcp-hub
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_mcp-hub
## bl get mcp-hub
List pre-built MCP servers available in the Blaxel Hub
### Synopsis
List pre-built MCP servers from the Blaxel Hub.
These provide ready-to-use tool integrations (e.g. GitHub, Slack,
databases). Connect one to your agent by creating an integration
connection with 'bl apply -f connection.yaml':
```yaml theme={null}
apiVersion: blaxel/v1alpha1
kind: IntegrationConnection
metadata:
name: my-github
spec:
integration:
```
Output formats:
-o json Machine-readable JSON array
-o yaml YAML output
default Table with NAME, INTEGRATION, DESCRIPTION columns
```
bl get mcp-hub [flags]
```
### Examples
```
# List all available MCP hub servers
bl get mcp-hub
# List as JSON (for automation/agents)
bl get mcp-hub -o json
# List as YAML
bl get mcp-hub -o yaml
```
### Options
```
-h, --help help for mcp-hub
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get models
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_models
## bl get models
List all models or get details of a specific one
```
bl get models [flags]
```
### Options
```
--all Fetch all pages (may be slow for large collections)
--cursor string Cursor from a previous page to fetch the next page of results
-h, --help help for models
--limit int Maximum number of items to return (auto-paginates when above 200) (default 200)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get policies
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_policies
## bl get policies
List all policies or get details of a specific one
```
bl get policies [flags]
```
### Options
```
--all Fetch all pages (may be slow for large collections)
--cursor string Cursor from a previous page to fetch the next page of results
-h, --help help for policies
--limit int Maximum number of items to return (auto-paginates when above 200) (default 200)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get previews
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_previews
## bl get previews
List all previews or get details of a specific one
```
bl get previews [flags]
```
### Options
```
-h, --help help for previews
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get previewtokens
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_previewtokens
## bl get previewtokens
List all previewtokens or get details of a specific one
```
bl get previewtokens [flags]
```
### Options
```
-h, --help help for previewtokens
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get sandbox-hub
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_sandbox-hub
## bl get sandbox-hub
List pre-built sandbox images available in the Blaxel Hub
### Synopsis
List pre-built sandbox images from the Blaxel Hub.
Each image comes with pre-installed tools, runtimes, and configurations.
Use the 'image' field value in your sandbox YAML spec when deploying
with 'bl apply -f sandbox.yaml':
```yaml theme={null}
apiVersion: blaxel/v1alpha1
kind: Sandbox
metadata:
name: my-sandbox
spec:
image:
```
Output formats:
-o json Machine-readable JSON array
-o yaml YAML output
default Table with NAME, IMAGE, MEMORY, DESCRIPTION columns
```
bl get sandbox-hub [flags]
```
### Examples
```
# List all available sandbox hub images
bl get sandbox-hub
# List as JSON (for automation/agents)
bl get sandbox-hub -o json
# List as YAML
bl get sandbox-hub -o yaml
```
### Options
```
-h, --help help for sandbox-hub
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get sandboxes
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_sandboxes
## bl get sandboxes
List all sandboxes or get details of a specific one
```
bl get sandboxes [flags]
```
### Options
```
--all Fetch all pages (may be slow for large collections)
--cursor string Cursor from a previous page to fetch the next page of results
-h, --help help for sandboxes
--limit int Maximum number of items to return (auto-paginates when above 200) (default 200)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get snapshots
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_snapshots
## bl get snapshots
List all snapshots or get details of a specific one
```
bl get snapshots [flags]
```
### Options
```
--all Fetch all pages (may be slow for large collections)
--cursor string Cursor from a previous page to fetch the next page of results
-h, --help help for snapshots
--limit int Maximum number of items to return (auto-paginates when above 200) (default 200)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get templates
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_templates
## bl get templates
List available project templates
### Synopsis
List available templates that can be used with 'bl new'.
Templates are grouped by type (agent, mcp, sandbox, job, volume-template).
Use an optional type argument to filter results.
Output formats:
-o json Machine-readable JSON array
-o yaml YAML output
default Table with NAME, TYPE, LANGUAGE, DESCRIPTION columns
```
bl get templates [type] [flags]
```
### Examples
```
# List all templates
bl get templates
# List agent templates only
bl get templates agent
# List templates as JSON
bl get templates -o json
# List MCP templates
bl get templates mcp
```
### Options
```
-h, --help help for templates
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get volumes
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_volumes
## bl get volumes
List all volumes or get details of a specific one
```
bl get volumes [flags]
```
### Options
```
--all Fetch all pages (may be slow for large collections)
--cursor string Cursor from a previous page to fetch the next page of results
-h, --help help for volumes
--limit int Maximum number of items to return (auto-paginates when above 200) (default 200)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl get volumetemplates
Source: https://docs.blaxel.ai/cli-reference/commands/bl_get_volumetemplates
## bl get volumetemplates
List all volumetemplates or get details of a specific one
```
bl get volumetemplates [flags]
```
### Options
```
-h, --help help for volumetemplates
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
--watch After listing/getting the requested object, watch for changes.
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl get](/cli-reference/commands/bl_get) - List or retrieve Blaxel resources in your workspace
# bl login
Source: https://docs.blaxel.ai/cli-reference/commands/bl_login
## bl login
Login to Blaxel
### Synopsis
Authenticate with Blaxel to access your workspace.
A workspace is your organization's isolated environment in Blaxel that contains
all your resources (agents, jobs, sandboxes, models, etc.). You must login before
using most Blaxel CLI commands.
Authentication Methods:
1. Browser OAuth (default) - Interactive login via web browser
2. API Key - For automation and scripts (set BL\_API\_KEY environment variable)
3. Client Credentials - For CI/CD pipelines (set BL\_CLIENT\_CREDENTIALS)
The CLI automatically detects which authentication method to use:
* If BL\_CLIENT\_CREDENTIALS is set, uses client credentials
* If BL\_API\_KEY is set, uses API key authentication
* Otherwise, shows interactive menu to choose browser or API key login
Credentials are stored securely in your system's credential store and persist
across sessions. Use 'bl logout' to remove stored credentials.
Examples:
```bash theme={null}
# Interactive login (shows menu to choose method)
bl login my-workspace
# Login without workspace (will prompt for workspace)
bl login
# API key authentication (non-interactive)
export BL_API_KEY=your-api-key
bl login my-workspace
# Client credentials for CI/CD
export BL_CLIENT_CREDENTIALS=your-credentials
bl login my-workspace
```
After logging in, all commands will use this workspace by default.
Override with --workspace flag: bl get agents --workspace other-workspace
```
bl login [workspace] [flags]
```
### Options
```
-h, --help help for login
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl logout
Source: https://docs.blaxel.ai/cli-reference/commands/bl_logout
## bl logout
Logout from Blaxel
### Synopsis
Remove stored credentials for a workspace.
This command clears local authentication tokens and credentials from your
system's credential store. Your deployed resources (agents, jobs, sandboxes)
continue running and are not affected by logout.
If you have multiple workspaces authenticated, you can logout from:
* A specific workspace by providing its name
* Any workspace interactively by running 'bl logout' without arguments
After logging out, you'll need to run 'bl login WORKSPACE' again to
authenticate before using other commands for that workspace.
Note: Logout is a local operation only. It does not:
* Stop running agents or jobs
* Delete any deployed resources
* Revoke tokens on the server (they will expire naturally)
* Affect other authenticated workspaces
Examples:
# Logout from current workspace (interactive selection)
bl logout
# Logout from specific workspace
bl logout my-workspace
# Login again after logout
bl login my-workspace
```
bl logout [workspace] [flags]
```
### Options
```
-h, --help help for logout
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl logs
Source: https://docs.blaxel.ai/cli-reference/commands/bl_logs
## bl logs
View and stream logs for agents, jobs, sandboxes, and functions
### Synopsis
View logs for Blaxel resources.
The logs command displays logs for agents, jobs, sandboxes, and functions.
You must specify both the resource type and resource name.
Resource Types (with aliases):
* sandboxes (sandbox, sbx)
* jobs (job, j, jb)
* agents (agent, ag)
* functions (function, fn, mcp, mcps)
Sandbox Process Logs:
For sandboxes, you can view logs for a specific process by adding the process name:
bl logs sandbox my-sandbox my-process
Job Execution Logs:
For jobs, you can filter logs by execution ID and task ID:
bl logs job my-job my-execution-id
bl logs job my-job my-execution-id my-task-id
Time Filtering:
By default, logs from the last 1 hour are displayed.
In follow mode (--follow), the last 15 minutes are shown as context, then new logs
are continuously streamed in real-time.
You can customize this by:
* Using duration format (e.g., 3d, 1h, 10m, 24h) with --period flag
* Using explicit start/end times with --start and --end flags
* Maximum time range is 3 days
Duration units:
* d: days
* h: hours
* m: minutes
* s: seconds
Timestamps:
By default, logs are prefixed with their timestamp in local timezone.
Use --no-timestamps to hide them, or --utc to display timestamps in UTC.
Severity Filtering:
By default, all severity levels are shown. Use --severity to filter by specific levels.
Available severities: FATAL, ERROR, WARNING, INFO, DEBUG, TRACE, UNKNOWN
Use comma-separated values: --severity ERROR,FATAL
Search:
Use --search to filter logs by text content. Only logs containing the search term will be displayed.
Examples:
# View logs for a specific sandbox (last 1 hour - default)
bl logs sandbox my-sandbox
# View logs for a specific process in a sandbox
bl logs sandbox my-sandbox my-process
# Stream process logs in real-time
bl logs sandbox my-sandbox my-process --follow
# View all logs for a job
bl logs job my-job
# View logs for a specific job execution
bl logs job my-job exec-abc123
# View logs for a specific task within an execution
bl logs job my-job exec-abc123 task-456
# Follow job execution logs in real-time
bl logs job my-job exec-abc123 --follow
# Follow logs in real-time (shows last 15 minutes, then streams new logs)
bl logs sandbox my-sandbox --follow
# Follow logs with more historical context
bl logs sandbox my-sandbox --follow --period 1h
# View logs from last 3 days
bl logs job my-job --period 3d
# View logs for a specific time range
bl logs agent my-agent --start 2024-01-01T00:00:00Z --end 2024-01-01T23:59:59Z
# Hide timestamps in output
bl logs agent my-agent --no-timestamps
# Show timestamps in UTC
bl logs agent my-agent --utc
# Filter by severity
bl logs agent my-agent --severity ERROR,FATAL
# Search for specific text in logs
bl logs agent my-agent --search "error"
# Using aliases
bl logs sbx my-sandbox --follow
bl logs j my-job --period 1h
bl logs fn my-function --follow
```
bl logs RESOURCE_TYPE RESOURCE_NAME [NESTED_ARGS...] [flags]
```
### Options
```
--end string End time for logs (RFC3339 format or YYYY-MM-DD)
-f, --follow Follow log output (like tail -f)
-h, --help help for logs
--no-timestamps Hide timestamps in log output
-p, --period string Time period to fetch logs (e.g., 3d, 1h, 10m, 24h)
--search string Search for logs containing specific text
--severity string Filter by severity levels (comma-separated): FATAL,ERROR,WARNING,INFO,DEBUG,TRACE,UNKNOWN
--start string Start time for logs (RFC3339 format or YYYY-MM-DD)
--utc Display timestamps in UTC instead of local timezone
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl mcp
Source: https://docs.blaxel.ai/cli-reference/commands/bl_mcp
## bl mcp
Serve the Blaxel MCP server to a coding agent, signed in with your bl login
### Synopsis
Serve the hosted Blaxel MCP tools over stdio using your existing bl login.
Run bl login before starting your agent. bl setup configures local MCP targets;
no token is stored in agent configurations and no separate MCP OAuth is needed.
Without a usable login, the connection still initializes, with an empty tool
list and instructions to run bl login, then restart or reconnect the agent.
The default workspace is pinned when the bridge starts resolving credentials:
the current bl workspace, --workspace or BL\_WORKSPACE. A tool's workspace
argument can intentionally select another authorized workspace. This pin is
not a restriction on the user's authority.
Tokens refresh in memory only. bl logout removes local credentials, affecting
future requests, but does not revoke refresh grants or work already in flight.
BL\_API\_KEY and BL\_CLIENT\_CREDENTIALS override stored credentials and are not
removed by bl logout. Environment inheritance varies between agent clients.
The HTTPS origin comes from the stored workspace environment (prod or dev),
not inherited BL\_API\_URL or BL\_ENV. Redirects are refused for both MCP requests
and token exchanges.
```
bl mcp [flags]
```
### Examples
```
claude mcp add --scope user blaxel -- bl mcp
{"mcpServers": {"blaxel": {"command": "/absolute/path/to/bl", "args": ["mcp"]}}}
```
### Options
```
-h, --help help for mcp
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl new
Source: https://docs.blaxel.ai/cli-reference/commands/bl_new
## bl new
Scaffold a new project from a template (agent, app, mcp, sandbox, job, volume-template)
### Synopsis
Create a new Blaxel resource from templates.
This command scaffolds a new project with the necessary configuration files,
dependencies, and example code to get you started quickly.
Resource Types:
agent - AI agent application that can chat, use tools, and access data
Use cases: Customer support bots, coding assistants, data analysts
app - Web application deployed on Blaxel infrastructure
Use cases: Next.js apps, web services, frontend applications
mcp - Model Context Protocol server that extends agent capabilities
Use cases: Custom tools, API integrations, database connectors
sandbox - Isolated execution environment for testing and running code
Use cases: Code execution, testing, isolated workloads
job - Batch processing task that runs on-demand or on schedule
Use cases: ETL pipelines, data processing, scheduled workflows,
GitHub Actions runners
volumetemplate - Pre-configured volume template for creating volumes
Use cases: Persistent storage templates, data volume configurations
Template Discovery:
Use --list to see all available templates with descriptions before creating.
Combine with a type argument to filter: 'bl new agent --list'
Interactive Mode (Recommended):
When called without arguments, the CLI guides you through:
1. Choosing a resource type
2. Selecting a template (language/framework)
3. Naming your project directory
4. Setting up initial configuration
Non-Interactive Mode:
Use --template and --yes flags for automation and CI/CD workflows.
After Creation:
1. cd into your new directory
2. Review and customize the generated blaxel.toml configuration
3. Follow the resource-specific instructions printed after scaffolding
4. Test the resource and deploy it when ready
```
bl new [type] [directory] [flags]
```
### Examples
```
# Interactive creation (recommended for beginners)
bl new
# Create agent interactively
bl new agent
# Create agent with specific template
bl new agent my-agent -t google-adk-py
# Create MCP server with default template (non-interactive)
bl new mcp my-mcp-server -y -t mcp-py
# Choose a sandbox type interactively
bl new sandbox
# Create Claude Code or Codex sandboxes non-interactively
bl new sandbox my-claude-sandbox -t claude-code -y
bl new sandbox my-codex-sandbox -t codex -y
# Create job with specific template
bl new job my-batch-job -t jobs-py
# Create a GitHub Actions runner job non-interactively
bl new job my-github-runner -t github-runner -y
# List all available templates
bl new --list
# List agent templates only
bl new agent --list
# List templates as JSON (for machine parsing)
bl new --list -o json
# Full workflow example:
bl new agent my-assistant
cd my-assistant
bl serve --hotreload # Test locally
bl deploy # Deploy to Blaxel
bl chat my-assistant # Chat with deployed agent
```
### Options
```
-h, --help help for new
-l, --list List available templates with descriptions
-t, --template string Template to use (skips interactive prompt)
-y, --yes Skip interactive prompts (job creation also requires --template)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl push
Source: https://docs.blaxel.ai/cli-reference/commands/bl_push
## bl push
Build and push a container image to the Blaxel registry
### Synopsis
Build and push a container image to the Blaxel registry without creating a deployment.
This command packages your code, uploads it, and builds a container image that
is stored in the workspace registry. Unlike 'bl deploy', this command does NOT
create or update any resource (agent, function, sandbox, or job).
The process includes:
1. Reading configuration from blaxel.toml
2. Packaging source code (respects .blaxelignore)
3. Uploading to Blaxel's build system via presigned URL
4. Building container image
5. Streaming build logs until the image is ready
If the blaxel.toml contains an 'image' field pointing to a registry image
(e.g. docker.io/myorg/myapp:latest), the platform will pull the image and
transform it for the target runtime via metamorph. If the same image was
already built, the build is triggered again by default. Use --skip-build to
skip the build if the image was already built.
Use --memory and --volume (MiB) to size the temporary build or import worker.
These flags override \[build].memoryMb and \[build].volumeMb in blaxel.toml.
Omitting both uses project settings or platform defaults; --volume 0 requests
memory-backed scratch. These settings do not change runtime resources.
For private registries, supply credentials via --registry-cred or --docker-config.
```
bl push [flags]
```
### Examples
```
# Push current directory as an image
bl push
# Push with a custom name
bl push --name my-image
# Push a specific subdirectory
bl push -d ./packages/my-agent
# Push specifying a resource type
bl push --type agent
# Push from a private registry (credentials for blaxel.toml image field)
bl push --registry-cred ghcr.io=user:token
# Skip rebuild if image was already built
bl push --skip-build
# Import a registry image with 16 GiB memory and 32 GiB scratch disk
bl push --image docker.io/myorg/myapp:latest --type sandbox --memory 16384 --volume 32768
# Push with a longer timeout for large images
bl push --timeout 30m
```
### Options
```
--build-env-file string Path to a build env file with Docker build args (default: auto-detect .env.build)
-d, --directory string Source directory path
--docker-config string Path to a Docker config.json file with registry credentials
-h, --help help for push
--image string Existing registry image to import; overrides blaxel.toml image
--memory int Build or import worker memory in MiB (1-32768); overrides [build].memoryMb
-n, --name string Name for the image (defaults to directory name)
-c, --registry-cred stringArray Registry credentials (format: registry=username:password, repeatable)
--skip-build Skip the image build step (use existing built image if available)
--timeout string Timeout for build log monitoring (e.g. 30m, 1h). Defaults to 1h
-t, --type string Resource type (agent, function, sandbox, job). Defaults to blaxel.toml type; required if not set
--volume int Build or import scratch disk in MiB (0-131072); 0 uses memory-backed scratch; overrides [build].volumeMb
-y, --yes Skip interactive mode
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl run
Source: https://docs.blaxel.ai/cli-reference/commands/bl_run
## bl run
Execute a resource (agent, model, job, function, sandbox)
### Synopsis
Execute a Blaxel resource with custom input data.
Different resource types behave differently when run:
* agent: Send a single request (non-interactive, unlike 'bl chat')
Returns agent response for the given input
* model: Make an inference request to an AI model
Calls the model's API endpoint with your data
* job: Start a job execution with batch input
Processes multiple tasks defined in JSON batch file
* function/mcp: Invoke an MCP server function
Calls a specific tool or method
* sandbox (sbx): Execute a command in a sandbox VM
Runs shell commands via the sandbox process API
Local vs Remote:
* Remote (default): Runs against deployed resources in your workspace
* Local (--local): Runs against locally served resources (requires 'bl serve')
Input Formats:
* Inline JSON with --data json-object
* From file with --file path/to/input.json
Streaming:
When agents respond via SSE (Server-Sent Events), the CLI automatically detects
and parses the stream. Use --stream to explicitly request streaming mode and
print chunks in real-time as they arrive.
Advanced Usage:
Use --path, --method, and --params for custom HTTP requests to your resources.
This is useful for testing specific endpoints or non-standard API calls.
```
bl run resource-type resource-name [flags]
```
### Examples
```
# Run agent with inline data
bl run agent my-agent --data '{"inputs": "Summarize this text"}'
# Run agent with file input
bl run agent my-agent --file request.json
# Run agent with real-time streaming output
bl run agent my-agent --data '{"inputs": "hello"}' --stream
# Run agent with timeout
bl run agent my-agent --data '{"inputs": "hello"}' --timeout 120
# Run job with batch file
bl run job my-job --file batches/process-users.json
# Run job locally for testing (requires 'bl serve' in another terminal)
bl run job my-job --local --file batch.json
# Run job locally with 4 concurrent workers
bl run job my-job --local --file batch.json --concurrent 4
# Run model with custom endpoint
bl run model my-model --path /v1/chat/completions --data '{"messages": [...]}'
# Run with query parameters
bl run agent my-agent --data '{}' --params "stream=true" --params "max_tokens=100"
# Run with custom headers
bl run agent my-agent --data '{}' --header "X-User-ID: 123"
# Debug mode (see full request/response details)
bl run agent my-agent --data '{}' --debug
# Get JSON output for machine parsing
bl run agent my-agent --data '{"inputs": "hello"}' -o json
# List a directory in a sandbox via the filesystem API (double slash => absolute path, bypassing the sandbox workdir)
bl run sandbox my-sandbox --method GET --path /filesystem//tmp
# Read a file from a sandbox via the filesystem API
bl run sandbox my-sandbox --method GET --path /filesystem//app/main.py
# Execute a command in a sandbox
bl run sandbox my-sandbox --path /process --data '{"command": "echo hello"}'
# Execute a command and wait for it to complete (returns stdout/stderr in response)
bl run sandbox my-sandbox --path /process --data '{"command": "ls -al /app", "waitForCompletion": true}'
# Execute a command with a working directory and a process name
bl run sandbox my-sandbox --path /process --data '{"command": "npm install", "name": "install-deps", "workingDir": "/app"}'
# For complex commands (nested quotes, backslashes, multiline), save the JSON in a file with your editor then pass it via --file (no shell-escaping)
bl run sandbox my-sandbox --path /process --file ./process-payload.json
# Execute a long-running command with keep-alive (prevents sandbox auto-standby)
bl run sandbox my-sandbox --path /process --data '{"command": "npm run dev -- --port 3000", "name": "dev-server", "keepAlive": true}'
# You can also use the 'sbx' shorthand
bl run sbx my-sandbox --path /process --data '{"command": "python script.py", "waitForCompletion": true}'
```
### Options
```
-c, --concurrent int Number of concurrent workers for local job execution (default 1)
-d, --data string JSON body data for the inference request
--debug Debug mode
--directory string Directory to run the command from
-e, --env-file strings Environment file to load (default [.env])
-f, --file string Input from a file
--header stringArray Request headers in 'Key: Value' format. Can be specified multiple times
-h, --help help for run
--local Run locally
--method string HTTP method for the inference request (default "POST")
--params strings Query params sent to the inference request
--path string path for the inference request
-p, --port int Port to connect to when using --local (default 1338)
-s, --secrets strings Secrets to pass to the execution
--stream Stream SSE responses in real-time
--timeout int Request timeout in seconds (default: no timeout)
--upload-file string This transfers the specified local file to the remote URL
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl serve
Source: https://docs.blaxel.ai/cli-reference/commands/bl_serve
## bl serve
Start a local development server for your project
### Synopsis
Start a local development server for your Blaxel project.
This runs your agent or MCP server locally on your machine for rapid
development and testing. Perfect for the inner development loop where you
want to iterate quickly without deploying to the cloud.
Supported Languages:
* Python (requires pyproject.toml or requirements.txt)
* TypeScript/JavaScript (requires package.json)
* Go (requires go.mod)
Hot Reload:
Enable --hotreload to automatically restart your server when code changes
are detected. This dramatically speeds up development by eliminating manual
restarts.
Testing Locally:
While your server is running, test it with:
* bl chat agent-name --local (for agents)
* bl run agent agent-name --local --data '' (for agents)
Workflow:
1. bl serve --hotreload Start local server with auto-reload
2. Edit your code Make changes
3. Test immediately Server reloads automatically
4. bl deploy Deploy when ready
```
bl serve [flags]
```
### Examples
```
# Basic serve with hot reload (recommended)
bl serve --hotreload
# Serve on custom port
bl serve --port 8080
# Serve specific subdirectory in monorepo
bl serve -d packages/my-agent
# Serve with environment variables
bl serve -e .env.local
# Serve with secrets (for testing)
bl serve -s API_KEY=test-key -s DB_PASSWORD=secret
# Full development workflow
bl serve --hotreload # Terminal 1: Run server
bl chat my-agent --local # Terminal 2: Test agent
```
### Options
```
-d, --directory string Serve the project from a sub directory
-e, --env-file strings Environment file to load (default [.env])
-h, --help help for serve
-H, --host string Bind socket to this host. If 0.0.0.0, listens on all interfaces (default "0.0.0.0")
--hotreload Watch for changes in the project
-p, --port int Bind socket to this port (default 1338)
-r, --recursive Serve the project recursively (default true)
-s, --secrets strings Secrets to deploy
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl setup
Source: https://docs.blaxel.ai/cli-reference/commands/bl_setup
## bl setup
Set up Blaxel for your coding agents and log in
### Synopsis
Set up everything Blaxel needs on this machine, then log in.
For the coding agents found on this machine (Claude Code, Codex, Cursor, ...),
setup installs the Blaxel agent skills and adds two MCP servers: blaxel, to
manage your workspace resources, and blaxel-docs, to search the Blaxel
documentation. It then logs you in to Blaxel in your browser. The agents use
that login through bl mcp, so they need no sign-in of their own.
In a terminal, setup shows everything it found, selected, and installs it
when you press Enter; --yes installs it without showing the plan. Without a
terminal, setup installs the same defaults and skips the browser login, so
run bl login afterwards. Anonymous error reports are on unless you turn them
off (or set DO\_NOT\_TRACK=1).
Setup only adds what is missing, and it is safe to run again after
installing another agent. MCP server entries you configured yourself are left
unchanged; the hosted blaxel server added by earlier versions is switched to
bl mcp. A blaxel server that the Blaxel plugin provides is left to the plugin.
```
bl setup [flags]
```
### Examples
```
# See what setup found, then press Enter to install
bl setup
# Install the defaults without showing the plan
bl setup --yes
# Set up only Claude Code and Codex, without logging in
bl setup --agent claude-code,codex --skip-login
```
### Options
```
--agent strings Coding agents to set up instead of the detected ones (for example claude-code,codex)
-h, --help help for setup
--skip-login Do not log in to Blaxel
--skip-mcp Do not add the Blaxel MCP servers
--skip-skills Do not install the Blaxel agent skills
-y, --yes Install the defaults without showing the plan
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl share
Source: https://docs.blaxel.ai/cli-reference/commands/bl_share
## bl share
Share a resource with another workspace
### Synopsis
Share Blaxel resources with other workspaces in your account.
Currently supports sharing container images.
### Examples
```
# Share an image with another workspace
bl share image agent/my-agent --workspace other-workspace
```
### Options
```
-h, --help help for share
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
* [bl share image](/cli-reference/commands/bl_share_image) - Share an image with another workspace
# bl share image
Source: https://docs.blaxel.ai/cli-reference/commands/bl_share_image
## bl share image
Share an image with another workspace
### Synopsis
Share a container image with another workspace in your account.
Only the metadata is copied — the image data stays in the source workspace.
The image reference format is: resourceType/imageName
* resourceType: Type of resource (e.g., agent, function, job, sandbox)
* imageName: The name of the image
```
bl share image resourceType/imageName [flags]
```
### Examples
```
# Share an image with another workspace
bl share image agent/my-agent --workspace other-workspace
```
### Options
```
-h, --help help for image
-w, --workspace string Target workspace to share with (required)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
```
### SEE ALSO
* [bl share](/cli-reference/commands/bl_share) - Share a resource with another workspace
# bl skills
Source: https://docs.blaxel.ai/cli-reference/commands/bl_skills
## bl skills
Manage Blaxel skills for coding agents
### Options
```
-h, --help help for skills
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
* [bl skills install](/cli-reference/commands/bl_skills_install) - Install or refresh Blaxel skills for coding agents
# bl skills install
Source: https://docs.blaxel.ai/cli-reference/commands/bl_skills_install
## bl skills install
Install or refresh Blaxel skills for coding agents
### Synopsis
Install the latest Blaxel agent skills globally from github.com/blaxel-ai/agent-skills.
Skills go to \~/.agents/skills and to the coding agents detected on this machine
(Claude Code, Codex, Cursor, ...). Nothing else needs to be installed first.
This explicit command runs even when automatic installation is disabled with
BL\_INSTALL\_SKILLS=false or in CI.
Existing externally managed skill links are kept, not refreshed. Same-name
skills in nested folders are reused automatically. Conflicting copies are
backed up outside the agents' skills folders and replaced with links.
```
bl skills install [flags]
```
### Options
```
-h, --help help for install
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl skills](/cli-reference/commands/bl_skills) - Manage Blaxel skills for coding agents
# bl token
Source: https://docs.blaxel.ai/cli-reference/commands/bl_token
## bl token
Retrieve authentication token for a workspace
### Synopsis
Retrieve the authentication token for the specified workspace.
The token command displays the current authentication token used by the CLI
for API requests. This token is automatically managed and refreshed as needed.
Authentication Methods:
* API Key: Returns the API key
* OAuth (Browser Login): Returns the access token (refreshed if needed)
* Client Credentials: Returns the access token (refreshed if needed)
The token is retrieved from your stored credentials and will be automatically
refreshed if it's expired or about to expire.
Examples:
```bash theme={null}
# Get token for current workspace
bl token
# Get token for specific workspace
bl token my-workspace
# Use in scripts (get just the token value)
export TOKEN=$(bl token)
```
```
bl token [workspace] [flags]
```
### Options
```
-h, --help help for token
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl unshare
Source: https://docs.blaxel.ai/cli-reference/commands/bl_unshare
## bl unshare
Unshare a resource from another workspace
### Synopsis
Remove shared Blaxel resources from other workspaces.
Currently supports unsharing container images.
### Examples
```
# Unshare an image from another workspace
bl unshare image agent/my-agent --workspace other-workspace
```
### Options
```
-h, --help help for unshare
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
* [bl unshare image](/cli-reference/commands/bl_unshare_image) - Unshare an image from another workspace
# bl unshare image
Source: https://docs.blaxel.ai/cli-reference/commands/bl_unshare_image
## bl unshare image
Unshare an image from another workspace
### Synopsis
Remove a shared image from another workspace.
This removes the metadata copy from the target workspace.
The original image in the source workspace is not affected.
The image reference format is: resourceType/imageName
* resourceType: Type of resource (e.g., agent, function, job, sandbox)
* imageName: The name of the image
```
bl unshare image resourceType/imageName [flags]
```
### Examples
```
# Unshare an image from another workspace
bl unshare image agent/my-agent --workspace other-workspace
```
### Options
```
-h, --help help for image
-w, --workspace string Target workspace to unshare from (required)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
```
### SEE ALSO
* [bl unshare](/cli-reference/commands/bl_unshare) - Unshare a resource from another workspace
# bl upgrade
Source: https://docs.blaxel.ai/cli-reference/commands/bl_upgrade
## bl upgrade
Upgrade the Blaxel CLI to the latest version
### Synopsis
Upgrade the Blaxel CLI to the latest version.
This command automatically detects your installation method and updates
the CLI in the correct location to avoid version conflicts.
Supported installation methods:
* Homebrew (brew)
* Manual installation (install.sh, or install.ps1 on Windows)
* Direct binary download
After upgrading, the Blaxel agent skills ([https://github.com/blaxel-ai/agent-skills](https://github.com/blaxel-ai/agent-skills))
are installed or refreshed globally so coding agents (Claude Code, Codex, Cursor, ...)
stay up to date. Set BL\_INSTALL\_SKILLS=false to skip this.
Examples:
# Upgrade to the latest version
bl upgrade
# Upgrade to a specific version
bl upgrade --version v1.2.3
# Force reinstall even if already on latest version
bl upgrade --force
```
bl upgrade [flags]
```
### Options
```
-f, --force Force reinstall even if already on latest version
-h, --help help for upgrade
--version string Target version to upgrade to (e.g., v1.2.3)
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl version
Source: https://docs.blaxel.ai/cli-reference/commands/bl_version
## bl version
Print the version number
```
bl version [flags]
```
### Options
```
-h, --help help for version
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# bl workspaces
Source: https://docs.blaxel.ai/cli-reference/commands/bl_workspaces
## bl workspaces
List workspaces or switch the current workspace
### Synopsis
List and manage Blaxel workspaces.
A workspace is an isolated environment within Blaxel that contains your
resources (agents, jobs, models, sandboxes, etc.). Workspaces provide:
* Isolation between projects or environments (dev/staging/prod)
* Separate billing and resource quotas
* Team collaboration boundaries
* Independent access control and permissions
The current workspace (marked with \*) determines where commands operate.
All commands like 'bl deploy', 'bl get', 'bl run' use the current workspace
unless you override with the --workspace flag.
To switch workspaces, provide the workspace name as an argument.
To list all authenticated workspaces, run without arguments.
```
bl workspaces [workspace] [flags]
```
### Examples
```
# List all authenticated workspaces
bl workspaces
# Switch to different workspace
bl workspaces production
# Use specific workspace for one command (doesn't switch current)
bl get agents --workspace staging
# Get only the current workspace name
bl workspaces --current
# Common multi-workspace workflow
bl workspaces dev # Switch to dev
bl deploy # Deploy to dev
bl workspaces prod # Switch to prod
bl deploy # Deploy to prod
```
### Options
```
--current Display only the current workspace name
-h, --help help for workspaces
```
### Options inherited from parent commands
```
-o, --output string Output format. One of: pretty,yaml,json,table
--skip-version-warning Skip version warning
-u, --utc Enable UTC timezone
-v, --verbose Enable verbose output
-w, --workspace string Specify the workspace name
```
### SEE ALSO
* [bl](/cli-reference/commands/bl) - Blaxel CLI - manage and deploy AI agents, sandboxes, and resources
# Blaxel CLI overview
Source: https://docs.blaxel.ai/cli-reference/introduction
Install the Blaxel CLI (bl) to manage sandboxes, agents, MCP servers, jobs, and other resources from your terminal, with auth and command reference.
Blaxel CLI (`bl`) is a command line tool to interact with the Blaxel APIs.
## Install
Run the installer in a terminal:
```shell macOS and Linux theme={null}
curl -fsSL https://blaxel.ai/install.sh | sh
```
```powershell Windows theme={null}
irm https://blaxel.ai/install.ps1 | iex
```
The installer checks the Blaxel CLI (`bl`) against the release checksums, installs it, and adds it to your `PATH`. On macOS and Linux, it also installs completions for your current shell if you use zsh, bash, or fish. On Windows, [configure PowerShell completions](/cli-reference/introduction#shell-autocompletion) separately. It then runs [`bl setup`](/cli-reference/commands/bl_setup), which shows what it found and installs it when you press Enter: the [Blaxel skills](/skills-mcp#blaxel-skills) and MCP servers for the coding agents on your machine (Claude Code, Codex, Cursor and others), then the browser login. Run it again at any time to add what is missing.
```shell theme={null}
brew install blaxel-ai/blaxel/blaxel
bl setup
```
Homebrew 6.0.0+ requires explicit trust for non-official taps, so use the fully qualified formula name.
On macOS and Linux, set these variables for `sh`:
| Variable | Effect |
| - | - |
| `VERSION=v0.1.119` | Install that release instead of the latest |
| `BINDIR=/usr/local/bin` | Install to that folder instead of `~/.local/bin` |
| `BL_INSTALL_SETUP=false` | Install the CLI only, and run `bl setup` later |
| `BL_INSTALL_SKILLS=false` | Leave your coding agents alone |
| `BL_INSTALL_PATH=false` | Do not add `bl` to your `PATH` |
| `BL_INSTALL_COMPLETION=false` | Skip the shell completions |
For example, to install a specific release for all users:
```shell theme={null}
curl -fsSL https://blaxel.ai/install.sh | sudo VERSION=v0.1.119 BINDIR=/usr/local/bin sh
```
Under `sudo`, the installer still sets up your own shell, coding agents and login. In CI, it installs the CLI only and leaves the shell configuration and coding agents alone. On Windows, run `$env:BL_INSTALL_SETUP = "false"` before the installer to install the CLI only.
## Update
Update Blaxel CLI by running the following command in a terminal:
```shell theme={null}
bl upgrade
```
If you need to update to a specific version (e.g. v0.1.21):
```shell theme={null}
bl upgrade --version 0.1.21
```
You can also upgrade using Homebrew:
```shell theme={null}
brew upgrade blaxel
```
Update Blaxel CLI by running the following command in a terminal:
```shell theme={null}
bl upgrade
```
If you need to update to a specific version (e.g. v0.1.21):
```shell theme={null}
bl upgrade --version 0.1.21
```
Run the installer again in PowerShell. It installs the latest release and sets up anything new:
```powershell theme={null}
irm https://blaxel.ai/install.ps1 | iex
```
## Shell autocompletion
On macOS and Linux, the installer sets up autocompletion for your current shell if you use zsh, bash, or fish. To set it up yourself, or for PowerShell, run one of the following:
```zsh zsh theme={null}
echo 'eval "$(bl completion zsh)"' >> ~/.zshrc
```
```bash bash theme={null}
echo 'eval "$(bl completion bash)"' >> ~/.bashrc
```
```fish fish theme={null}
echo 'bl completion fish' > ~/.config/fish/completions/bl.fish
```
```powershell powershell theme={null}
if (!(Test-Path -Path $PROFILE)) {
New-Item -ItemType File -Path $PROFILE -Force
}
Add-Content -Path $PROFILE -Value '(& bl completion powershell) | Out-String | Invoke-Expression'
```
## Get started
First, create a [workspace](../Security/Workspace-access-control) on the Blaxel console. The installer logs you in; to log in again, run:
```bash theme={null}
bl login
```
If you have multiple workspaces, the one you last logged into will be used automatically in every subsequent command you run. You can log in to as many workspaces as you want.
To set your context to a different workspace you already logged in to, use the following command:
```bash theme={null}
bl workspaces your-workspace
### You can retrieve the list of all your workspaces by running:
bl workspaces
```
Basic commands to scaffold, develop, and deploy your resources:
| Command | Purpose |
| - | - |
| `bl new` | Scaffold a new local project (code + `blaxel.toml`) |
| `bl deploy` | Build image + push image + create/update resource + watch logs |
| `bl push` | Build image + push image to Blaxel registry (no resource created or updated) |
| `bl apply` | Declarative resource creation/update using YAML files, similar to `kubectl apply` |
| `bl serve` | Local execution *(not available for sandboxes)* |
`bl deploy` is an all-in-one command that packages local files into an image, pushes it as a template, and deploys it as a resource on Blaxel. Use the `--skip-build` flag to skip image rebuilding. Use `bl push` instead to only build and push the image.
Interact with existing resources in your workspace:
```shellscript theme={null}
bl get sandboxes
```
## **Options**
```text theme={null}
-h, --help Get the help for Blaxel
-w, --workspace string Specify the Blaxel workspace to work on.
-o, --output string Output format. One of: pretty, yaml, json, table
-v, --verbose Enable verbose output
```
# blaxel.toml deployment configuration file reference
Source: https://docs.blaxel.ai/deployment-reference
Complete reference for the blaxel.toml configuration file, including resource types, build settings, runtime options, environment variables, and volumes.
You can customize your Blaxel deployment with Blaxel's configuration file `blaxel.toml`.
The format of Blaxel's configuration file is described below.
```toml theme={null}
# resource type (optional)
# possible values: ["agent", "function", "job", "sandbox", "volume-template"]
# type = "agent"
# resource name (optional)
# defaults to the directory name
# name = "my-resource"
# registry image (optional, for resource type="sandbox")
# use a pre-built image from a private registry instead of building from a Dockerfile
# requires registry credentials in .docker/config.json or via bl push flags
# image = "docker.io/myuser/my-sandbox:latest"
# public access to resource (only valid for resource type="agent")
# possible values: [true, false]
# defaults to false
# public = false
# deployment region (optional, for resource type="agent", "function")
# pins the resource to a specific region instead of global distribution
# required when attaching volumes to an agent
# region = "us-pdx-1"
# build configuration (optional)
# [build]
# sandbox image slimming
# after the Docker build, removes a fixed set of build-time paths
# including package caches, docs, headers, and compilers such as make and gcc
# set to false to keep those files in the running sandbox
# possible values: [true, false]
# defaults to true
# slim = true
# memory of the environment the image is built in (MB, optional)
# not the memory of the deployed resource, see [runtime] below
# memoryMb = 8192
# disk attached to the build for its intermediate layers (MB, optional)
# unset means the build runs in memory, set it for large images
# volumeMb = 30000
# region the image is built in (optional)
# defaults to the platform's build region
# region = "us-was-1"
# Agent Drive used as a persistent layer cache (optional)
# must be in the same workspace and in the build region above
# Agent Drive is in private preview and only available in us-was-1
# unset means every build starts without a cache
# cacheDrive = "my-build-cache"
# build-time variables (optional)
# should be used for non-secret values only
# [build.args]
# MY_BUILD_VAR = "my-value"
# entrypoint configuration (optional)
# [entrypoint]
# entrypoint command for production environment
# prod = "python main.py"
# entrypoint command for development environment
# dev = "python main.py --dev"
# environment variables (optional)
# [env]
# key-value pairs
# MY_VAR = "my-value"
# runtime configuration (optional)
# [runtime]
# memory (MB)
# memory = 4096
# job configuration
# maxConcurrentTasks = 10
# task timeout in seconds (only valid for resource type="job")
# timeout = 900
# maximum number of retries
# maxRetries = 0
# volumes (optional, for resource type="agent", "sandbox", "volume-template", "job")
# attaches persistent storage to the resource
# the resource must be pinned to the same region as the volume
# [[volumes]]
# volume name
# name = "my-volume"
# volume type (only for resource type="job")
# possible values: ["ephemeral"]
# type = "ephemeral"
# volume mount path
# for resource type="agent" and "sandbox", attaches persistent storage
# for resource type="job", attaches ephemeral storage
# mountPath = "/data"
# volume size in MB (only valid for resource type="job" and volume type="ephemeral")
# sizeMb = 10240
# volume templates directory (only valid for resource type="volume-template")
# directory = "."
# volume default size in MB (only valid for resource type="volume-template")
# defaultSize = 1024
# job trigger (optional)
# [[triggers]]
# trigger identifier
# id = "my-trigger"
# trigger type
# possible values: ["cron", "http", "http-async"]
# "cron" trigger (only valid for resource type="job")
# type = "cron"
# "http" trigger (only valid for resource type="agent" and resource type="function")
# type = "http"
# "http-async" trigger (only valid for resource type="agent")
# type = "http-async"
# [[triggers.configuration]]
# cron expression (only for trigger type="cron")
# schedule = "0 * * * *"
# endpoint URL (only for trigger type="http" and trigger type="http-async")
# path = "/webhook"
# endpoint authentication (only for resource type="function" and trigger type="http")
# possible values: ["public", "private"]
# authenticationType = "public"
# callback URL (only for trigger type="http-async")
# callbackUrl = "https://webhook.site/3955e30a-e4b6-4fa5-8a28-598ce0f53386"
```
For sandbox images, see [image slimming](/Sandboxes/Templates#image-slimming) for the paths removed when `slim` is `true`.
Learn more about the commands and resources this file configures.
Deploy a resource from its blaxel.toml.
Shared filesystems you can mount into sandboxes and use as a build cache.
Block storage attached to a single sandbox at creation.
# Batch delete sandboxes from the CLI
Source: https://docs.blaxel.ai/knowledge-base/configuration-and-operation/batch-delete-sandboxes-from-the-cli
Delete all or selected Blaxel sandboxes in bulk with the CLI and jq filters.
Use the Blaxel CLI with `jq` to delete multiple sandboxes in one operation. This is useful for workspace cleanup and sandbox lifecycle management at scale.
Sandbox deletion is permanent. Run the inner `bl get` and `jq` command first to verify the names before passing them to `bl delete`.
## Delete all sandboxes
Delete every sandbox in the current workspace:
```bash theme={null}
bl delete sbx $(bl get sbx -o json | jq -r '.[].metadata.name')
```
## Delete specific sandboxes
Pass multiple sandbox names:
```bash theme={null}
bl delete sandbox sandbox-1 sandbox-2
```
## Delete sandboxes by status
Delete sandboxes with the `FAILED` status:
```bash theme={null}
bl delete sbx $(bl get sbx -o json | jq -r '.[] | select(.status == "FAILED") | .metadata.name')
```
## Delete sandboxes by label
Delete sandboxes whose `nextjs` label is set to `true`:
```bash theme={null}
bl delete sbx $(bl get sbx -o json | jq -r '.[] | select(.metadata.labels.nextjs == "true") | .metadata.name')
```
These commands parse the JSON response from `bl get`, select matching sandbox resources, and pass their names to `bl delete`.
Review deletion syntax and additional bulk deletion examples.
Automate sandbox cleanup with TTL policies.
# Ensure sandboxes use the latest deployed image
Source: https://docs.blaxel.ai/knowledge-base/configuration-and-operation/ensure-sandboxes-use-the-latest-deployed-image
Create or update Blaxel sandboxes with the latest deployed image version and understand how existing sandboxes behave.
Use the `latest` tag when new sandboxes should include the most recently deployed packages and dependencies. Existing sandboxes continue to use their current image until you replace or update them.
## Use the latest tag
Reference your sandbox template image with the `:latest` tag:
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.create({
name: "my-sandbox",
image: "my-image:latest",
});
```
If you omit a tag, `latest` is the default. Each new sandbox then uses the newest deployed version available at creation time.
## Manage existing sandboxes
Running `bl deploy` does not update existing sandboxes that use an older image. They continue running until you explicitly replace or update them.
To move every sandbox to the latest image:
1. Delete sandboxes that use older image versions.
2. Create new sandboxes with the `:latest` tag.
Alternatively, call the UpdateSandbox API at `PUT /sandboxes/{sandboxName}` to update the image in place. Updating the image resets the sandbox state.
## Idempotent creation behavior
`createIfNotExists()` in TypeScript and `create_if_not_exists()` in Python return an existing sandbox with the same name unchanged. They do not recreate it with the latest image.
Delete the sandbox first or use the UpdateSandbox API when you need to force an image refresh.
## Persistence and fresh images
Blaxel sandboxes can remain in standby indefinitely, preserve their state between sessions, and resume in under 25 ms. This persistence can reduce setup time and compute costs.
If fresh packages are more important than state persistence, regularly replace your sandboxes and create them with the `:latest` tag.
Deploy new revisions of a custom sandbox image.
Choose a lifecycle strategy for persistent sandboxes.
# Getting static IP addresses for Blaxel sandboxes
Source: https://docs.blaxel.ai/knowledge-base/configuration-and-operation/getting-static-ip-addresses-for-blaxel-sandboxes
Retrieve the regional public IP address ranges used by Blaxel sandboxes for network allowlists.
If network restrictions require you to allowlist IP addresses for Blaxel deployments, including sandbox traffic, retrieve the current public IP ranges through the Blaxel API. The API returns addresses as CIDR ranges for each region.
## Retrieve public IP addresses
Send an authenticated request to the public IP endpoint:
```bash theme={null}
curl --request GET \
--url https://api.blaxel.ai/v0/publicIps \
--header "Authorization: Bearer $BL_API_KEY"
```
## IP address stability
Blaxel public IP addresses are static and do not change frequently. New addresses are typically added only as the platform scales, which happens a few times per year or less.
Retrieve the list from the API whenever you need the latest ranges.
## Regional IP addresses
Blaxel offers sandboxes in four regions:
* `us-pdx-1`: Portland
* `us-was-1`: North Virginia
* `eu-lon-1`: London
* `eu-fra-1`: Frankfurt
To retrieve addresses for one region, add the `region` query parameter:
```bash theme={null}
curl --request GET \
--url "https://api.blaxel.ai/v0/publicIps?region=us-was-1" \
--header "Authorization: Bearer $BL_API_KEY"
```
## Future networking options
Blaxel is working on:
* Dedicated IP addresses per customer without significant resource reservation
* Private endpoint connections to AWS VPCs
These options will provide additional networking controls for customers with strict security requirements.
Review regions and the public IP API.
Configure dedicated outbound networking for supported workloads.
# Sandbox inactivity with long-running processes
Source: https://docs.blaxel.ai/knowledge-base/configuration-and-operation/sandbox-inactivity-with-long-running-processes
Keep long-running sandbox processes active by waiting, polling from a backend, or using an external agent architecture.
Blaxel sandboxes enter standby when they have no active connections, even if processes are still running. This prevents background processes such as development servers from keeping sandboxes active indefinitely.
## Standby behavior
A sandbox can enter standby during a running command when:
* No active connections remain
* No API requests poll the sandbox for updates
This behavior also applies to `sandbox.process.exec` with `waitForCompletion: false`. The process can enter standby before it finishes.
## Wait for the process
Use `sandbox.process.wait` to maintain polling while a long-running process executes. Set a maximum wait duration appropriate for your workload and choose a polling interval that meets your latency requirements.
```typescript theme={null}
await sandbox.process.exec({
name: "long-task",
command: "run-long-task",
waitForCompletion: false,
});
await sandbox.process.wait("long-task", {
maxWait: 600000,
interval: 5000,
});
```
## Poll from your backend
Use a backend service for polling instead of relying entirely on a browser. Backend polling continues if a user closes the browser and prevents the sandbox from entering standby during a critical process.
## Use an external agent
You can move the agent outside the sandbox and connect it to the sandbox through tools. This architecture can provide more direct lifecycle control, but it may be more complex depending on your use case and toolchain.
Wait for processes and configure keep-alive behavior.
Understand connections and automatic standby.
# Understanding sandbox active vs standby states and billing
Source: https://docs.blaxel.ai/knowledge-base/configuration-and-operation/understanding-sandbox-active-vs-standby-states-and-billing
Understand how active and standby sandbox states affect compute, storage, connections, and billing.
Blaxel sandboxes operate in active and standby states. These states determine which resources are billed and how quickly a sandbox can resume.
## Active mode
A sandbox stays active while it has an active connection. In active mode, you are charged for allocated memory and storage.
Blaxel allocates CPU based on your selected memory allocation. CPU is not charged separately.
Active connection behavior includes:
* HTTP, WebSocket, or TCP connections with data flow keep a sandbox active
* Idle connections, such as WebSockets with no data flow, have a 15-minute timeout
* After 15 minutes with no data transmission, the sandbox transitions to standby
See [standby control](/Sandboxes/Standby-control) for ways to manage connections and activity.
## Standby mode
Without active connections, a sandbox transitions to standby in approximately 15 seconds.
During the transition:
* Blaxel snapshots the complete state, including the filesystem and running processes
* You are not charged for CPU or memory while the sandbox is in standby
* You are charged only for snapshot and volume storage
* Reconnecting restores the sandbox in under 25 ms with its processes preserved
## Common issues preventing standby
### Missing sandbox API
A missing or outdated sandbox API is the most common reason a sandbox fails to enter standby. The sandbox API is required for correct shutdown behavior.
To correct the configuration:
1. Use the latest sandbox API in your Dockerfile.
2. Copy the binary into your image:
```dockerfile theme={null}
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
```
3. Start the sandbox API first in your entrypoint and wait for it to become ready on port `8080` before starting other processes.
4. Delete and recreate the sandbox after updating the image.
### Preview URL connections
Deployed preview URLs do not maintain persistent connections and do not prevent standby. You do not need to recreate previews to allow a sandbox to enter standby.
Each incoming request through a preview URL wakes the sandbox.
### Process keep-alive
Process keep-alive controls automatic hibernation. Starting a process with `keepAlive: true` in TypeScript or `keep_alive: True` in Python keeps the sandbox active until the process completes or times out.
The default timeout is 600 seconds. Set the timeout to `0` for a process that should run until it completes naturally.
## Monitor sandbox state
The API reports `DEPLOYED` for both active and standby sandboxes. Use these methods to distinguish them:
* Check CPU or memory charts in the Blaxel Console. A value of 0% indicates that the sandbox was in standby
* Use Billing Explorer to inspect compute runtime charges, measured in GB-seconds
* Treat continuous compute runtime charges as a sign that the sandbox may be stuck in active mode
## Troubleshoot continuous billing
If a sandbox that should be idle incurs continuous compute charges:
1. Verify that the sandbox API is installed and running.
2. Close every connection when it is idle.
3. Check for background processes that keep connections open.
4. Delete and recreate the sandbox after correcting the sandbox API.
A correctly configured sandbox incurs compute charges only while it actively processes requests.
Manage active connections and automatic standby behavior.
Keep a sandbox active while selected processes run.
# Understanding sandbox TTL and expiration policies
Source: https://docs.blaxel.ai/knowledge-base/configuration-and-operation/understanding-sandbox-ttl-and-expiration-policies
Configure sandbox TTL and expiration policies to control lifecycle, automatic deletion, and storage costs.
Blaxel provides TTL and expiration policies that automatically manage sandbox lifecycles. Use them to control costs and remove unused sandboxes from your workspace.
## TTL policy types
Configure any of these lifecycle policies:
* `ttl-idle`: Delete a sandbox after a specified period of inactivity
* `ttl-max-age`: Delete a sandbox after a maximum age, regardless of activity
* `date`: Delete a sandbox on a specific date
You can also define TTL through top-level sandbox parameters:
* `ttl`: Set a maximum age with simpler syntax
* `expires`: Set an absolute expiration date
## Configure TTL policies
Set `ttl`, `expires`, or `lifecycle` when creating a sandbox:
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.create({
name: "my-sandbox",
image: "my-image",
ttl: "24h",
// OR
// expires: new Date(Date.now() + 86400000),
// OR / AND
lifecycle: {
expirationPolicies: [
{ type: "ttl-idle", value: "1h", action: "delete" },
],
},
});
```
## Policy behavior
### Idle TTL timing
The `ttl-idle` timer starts only after the sandbox receives its first API call or connection. Creating a sandbox without using it does not start the idle timer.
On Tier 1, a hard 30-day cap still applies. A sandbox is deleted after 30 days regardless of activity, so `ttl-idle` cannot provide rolling expiration. Rolling expiration based on activity is available on Tier 2 and above.
### Processing frequency
TTL enforcement does not operate at one-second precision. The cleanup process runs approximately every 60 seconds.
### Computed termination time
Sandbox metadata includes the read-only `expiresIn` field in TypeScript and `expires_in` field in Python. It reports the number of seconds until automatic termination under the current TTL or lifecycle policy.
## Deletion process
When a sandbox expires:
1. Blaxel terminates the sandbox.
2. The sandbox remains in the `terminated` state for a retention period, which depends on your tier configuration.
3. Blaxel deletes the sandbox completely.
## Tier limits
TTL restrictions depend on your account tier:
* Tier 0: A maximum TTL of 7 days is enforced
* Tier 1: A hard 30-day maximum lifetime is enforced, and `expiresIn` reflects this limit
* Tier 2 and above: No maximum TTL is enforced, and `ttl-idle` supports rolling expiration based on activity
## Idempotent sandbox creation
When you call `createIfNotExists` with the same sandbox name, Blaxel replaces an existing terminated sandbox with a new one.
Use TypeScript SDK v0.2.45 or later or Python SDK v0.2.21 or later for this behavior.
## Update an existing sandbox
Apply a TTL to an existing sandbox with `updateTtl` or update its lifecycle policies. This is useful when cleaning up sandboxes created without expiration rules.
See [sandbox expiration policies](/Sandboxes/Expiration#update-sandbox-expiry-rules) for TypeScript and Python examples.
## Preview cleanup
Deleting a sandbox, including automatic deletion through TTL, also removes all associated preview URLs. You do not need to delete previews separately because they are scoped to the sandbox.
Configure, update, and clear sandbox expiry rules.
Apply TTLs as part of a lifecycle management strategy.
# Use template names instead of image IDs when creating sandboxes
Source: https://docs.blaxel.ai/knowledge-base/configuration-and-operation/use-template-names-instead-of-image-id-when-creating-sandboxes
Create Blaxel sandboxes from consistent template names instead of workspace-specific image IDs.
You can use a template name instead of an `IMAGE_ID` when creating a sandbox. This keeps code readable and portable across development and production workspaces.
## Create from a template name
Pass the deployed template name to the `image` property:
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.create({
name: "my-sandbox",
image: "my-template",
memory: 4096,
ports: [
{ name: "nextjs-dev", target: 3000 },
{ name: "another-api", target: 8888 },
],
});
```
## Requirements
* Deploy the template in every workspace where you use it
* Keep the template name consistent across workspaces
## Benefits
Using a template name provides:
* More readable and maintainable code
* Consistent references across environments
* No need to retrieve and hardcode workspace-specific image IDs
For example, you no longer need a lookup command like:
```bash theme={null}
bl get sandboxes mytemplate -o json | jq -r '.[0].spec.runtime.image'
```
Build, deploy, and use reusable sandbox images.
Review sandbox creation and configuration options.
# Using custom domains with sandboxes
Source: https://docs.blaxel.ai/knowledge-base/configuration-and-operation/using-custom-domains-with-sandboxes
Configure verified custom domains and wildcard subdomains for Blaxel sandbox preview URLs.
You can use custom domains with sandbox preview URLs. Registering a domain automatically enables wildcard subdomains.
For example:
* Registering `mycompany.com` lets you use any `*.mycompany.com` subdomain
* Registering `preview.mycompany.com` lets you use any `*.preview.mycompany.com` subdomain
## Create a preview URL
Use the SDK to create a sandbox preview on your custom domain:
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandbox = await SandboxInstance.get("my-sandbox");
const preview = await sandbox.previews.create({
metadata: { name: "preview-custom-domain" },
spec: {
port: 443,
public: false,
customDomain: "your.custom.domain.dev",
},
});
```
This approach lets you:
* Give the preview a specific name
* Retrieve the preview later with the SDK
* Access the custom domain URL from the preview specification
## Requirements and limitations
* Custom domains require Tier 3 or higher access
* Each custom domain is assigned to one region and cannot be used across regions
* You must configure the required DNS records and verify the domain in the Blaxel Console
* Custom domains currently work only for sandbox previews
Register, verify, and manage custom domains.
Create and manage public or private sandbox previews.
# Volume resizing for running sandboxes
Source: https://docs.blaxel.ai/knowledge-base/configuration-and-operation/volume-resizing-for-running-sandboxes
Increase attached volume capacity while a Blaxel sandbox is running and automate resizing based on disk usage.
You can resize a volume while its sandbox is running. The operation typically completes in under one second.
## Limitations
* You can increase a volume's size but cannot decrease it
* Blaxel does not currently provide built-in automatic resizing
## Resizing strategy
To avoid running out of disk space:
* Check volume usage before file-intensive commands such as package installations
* Resize the volume when usage approaches 80%
* Run a periodic monitor in the sandbox to measure usage and trigger resizing
## Monitor and resize a volume
The following function checks disk usage every three seconds and doubles the volume size when usage reaches 80%:
```typescript theme={null}
import { SandboxInstance, VolumeInstance } from "@blaxel/core";
async function startVolumeMonitor(
sandbox: SandboxInstance,
volumeName: string,
mountPath: string,
initialSizeMB: number,
usageThresholdPercent: number = 80,
growthFactor: number = 2,
pollIntervalSeconds: number = 3,
) {
let currentSizeMB = initialSizeMB;
let resizing = false;
const monitorCmd = `while true; do df -m ${mountPath}; sleep ${pollIntervalSeconds}; done`;
await sandbox.process.exec({
command: monitorCmd,
onLog: (log) => {
const lines = log.split("\n");
for (const line of lines) {
if (!line.includes(mountPath) || line.startsWith("Filesystem")) continue;
const parts = line.trim().split(/\s+/);
if (parts.length < 5) continue;
const usagePercent = parseInt(parts[4]!.replace("%", ""), 10);
if (isNaN(usagePercent)) continue;
console.log(
`[monitor] ${parts[2]}MB used / ${parts[1]}MB total (${usagePercent}%)`,
);
if (usagePercent >= usageThresholdPercent && !resizing) {
resizing = true;
const newSize = Math.ceil(currentSizeMB * growthFactor);
console.log(
`[monitor] Usage ${usagePercent}% >= ${usageThresholdPercent}%, resizing ${currentSizeMB}MB to ${newSize}MB`,
);
VolumeInstance.update(volumeName, { size: newSize })
.then(() => {
currentSizeMB = newSize;
console.log(`[monitor] Resize to ${newSize}MB complete`);
})
.catch((err) => {
console.error("[monitor] Resize failed:", err);
})
.finally(() => {
resizing = false;
});
}
}
},
});
return {
getCurrentSize: () => currentSizeMB,
};
}
```
Adjust the threshold, growth factor, and polling interval for your workload.
Create, inspect, resize, and delete volumes.
Attach persistent storage to a sandbox.
# Set up Astro with Blaxel Sandboxes
Source: https://docs.blaxel.ai/knowledge-base/integrations/setting-up-astro-with-blaxel-sandboxes
Run an Astro application in a Blaxel Sandbox and expose it securely with a Blaxel preview URL.
This guide explains how to run an Astro application inside a Blaxel Sandbox and expose it securely using Blaxel previews.
## Prerequisites
* [Blaxel CLI](../../cli-reference/introduction) installed and authenticated with `bl login`
* Node.js 18 or later
* `@blaxel/core` installed in your project with `npm install @blaxel/core`
## 1. Understand the architecture
Running Astro inside a Blaxel Sandbox requires a few adjustments:
* Astro normally binds to localhost
* Blaxel exposes services through preview URLs
* Astro must accept external connections and all hosts
Configure `astro.config.mjs` with `host: "0.0.0.0"` and `allowedHosts: true`, then expose the Astro development server through a [Blaxel preview URL](../../Sandboxes/Preview-url).
## 2. Build the sandbox image
Create a `Dockerfile`:
```dockerfile theme={null}
FROM oven/bun:alpine
RUN apk update && apk add --no-cache \
git \
curl \
netcat-openbsd \
nodejs \
npm \
&& rm -rf /var/cache/apk/*
WORKDIR /app
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
# Create the Astro project with npx, then use Bun for dependencies
RUN npx create-astro@latest /app --template basics --no-install --no-git --yes \
&& bun install
COPY ./astro.config.mjs /app/astro.config.mjs
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
```
## 3. Configure Astro
Create `astro.config.mjs` to allow external connections:
```javascript theme={null}
// @ts-check
import { defineConfig } from "astro/config";
export default defineConfig({
server: {
host: "0.0.0.0",
port: 4321,
allowedHosts: true,
},
});
```
## 4. Create the entrypoint
Create `entrypoint.sh` to start the sandbox API and Astro development server:
```bash theme={null}
#!/bin/sh
export PATH="/usr/local/bin:$PATH"
/usr/local/bin/sandbox-api &
wait_for_port() {
local port=$1
local timeout=30
local count=0
echo "Waiting for port $port to be available..."
while ! nc -z localhost "$port"; do
sleep 1
count=$((count + 1))
if [ "$count" -gt "$timeout" ]; then
echo "Timeout waiting for port $port"
exit 1
fi
done
echo "Port $port is now available"
}
wait_for_port 8080
echo "Running Astro dev server..."
curl http://localhost:8080/process \
-X POST \
-H "Content-Type: application/json" \
-d '{
"name": "dev-server",
"workingDir": "/app",
"command": "bun run dev",
"waitForCompletion": false,
"restartOnFailure": true,
"maxRestarts": 25
}'
wait
```
## 5. Configure and deploy the image
Create `blaxel.toml` beside the `Dockerfile`:
```toml theme={null}
type = "sandbox"
name = "astro-template"
[runtime]
memory = 4096
[[runtime.ports]]
name = "astro-dev"
target = 4321
protocol = "tcp"
```
Deploy the image:
```bash theme={null}
bl deploy
```
## 6. Create or reuse a sandbox
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-astro-sandbox";
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "astro",
},
image: "astro-template:latest",
memory: 4096,
ports: [
{ name: "preview", target: 4321, protocol: "HTTP" },
],
});
```
## 7. Configure preview response headers
Astro development servers work well with permissive CORS headers when accessed through a preview URL:
```typescript theme={null}
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
```
Alternatively, use [custom domains](../../Infrastructure/Custom-domains) to expose previews on your own domain.
## 8. Create the preview and token
Astro runs on port `4321`. Create a private preview for that port:
```typescript theme={null}
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "dev-server-preview" },
spec: {
responseHeaders,
public: false,
port: 4321,
},
});
```
Generate a token that expires after one day:
```typescript theme={null}
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
const token = await preview.tokens.create(expiresAt);
```
## 9. Start and monitor the development server
If you do not use the entrypoint script, start Astro programmatically:
```typescript theme={null}
async function startDevServer(sandbox: SandboxInstance) {
console.log("Starting Astro dev server...");
await sandbox.process.exec({
name: "dev-server",
command: "bun run dev",
workingDir: "/app",
waitForPorts: [4321],
restartOnFailure: true,
maxRestarts: 25,
});
}
```
Stream its logs:
```typescript theme={null}
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log);
},
});
logStream.close();
```
## 10. Access the Astro application
Once the server is running, open:
```text theme={null}
${preview.spec?.url}?bl_preview_token=${token.value}
```
## 11. Run the complete example
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-astro-sandbox";
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
async function startDevServer(sandbox: SandboxInstance) {
await sandbox.process.exec({
name: "dev-server",
command: "bun run dev",
workingDir: "/app",
waitForPorts: [4321],
restartOnFailure: true,
maxRestarts: 25,
});
}
async function main() {
try {
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "astro",
},
image: "astro-template:latest",
memory: 4096,
ports: [
{ name: "preview", target: 4321, protocol: "HTTP" },
],
});
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "preview" },
spec: {
responseHeaders,
public: false,
port: 4321,
},
});
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
const token = await preview.tokens.create(expiresAt);
const processes = await sandbox.process.list();
if (!processes.find((process) => process.name === "dev-server")) {
await startDevServer(sandbox);
}
const webUrl = `${preview.spec?.url}?bl_preview_token=${token.value}`;
console.log(`Astro Preview URL: ${webUrl}`);
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log);
},
});
process.on("SIGINT", () => {
logStream.close();
process.exit(0);
});
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();
```
This setup runs Astro fully inside a Blaxel Sandbox, securely exposes the development server, supports hot module replacement, and uses Bun for fast dependency installation and development server performance. It is suitable for static sites, blogs, documentation, preview environments, internal demos, and AI-powered coding workflows.
## Resources
Configure public and private preview URLs for sandbox services.
Build and manage reusable Blaxel Sandbox images.
# Set up Expo with Blaxel Sandboxes
Source: https://docs.blaxel.ai/knowledge-base/integrations/setting-up-expo-with-blaxel-sandboxes
Run an Expo React Native and web application in a Blaxel Sandbox and access it through secure preview URLs.
This guide explains how to run an Expo React Native and web application inside a Blaxel Sandbox, expose it securely using Blaxel previews, and access it on a mobile device.
## Prerequisites
* [Blaxel CLI](../../cli-reference/introduction) installed and authenticated with `bl login`
* Node.js 18 or later
* `@blaxel/core` installed in your project with `npm install @blaxel/core`
## 1. Understand the architecture
Running Expo inside a Blaxel Sandbox requires a few adjustments:
* Expo normally binds to local or internal URLs
* Blaxel exposes services through preview URLs
* Expo must use those preview URLs explicitly
Inject the preview URL into `app.json`, set `EXPO_PACKAGER_PROXY_URL`, restart the development server after configuration changes, and expose the Expo server through a [Blaxel preview URL](../../Sandboxes/Preview-url).
## 2. Build the sandbox image
Create a `Dockerfile`:
```dockerfile theme={null}
FROM node:22-alpine
RUN apk update && apk add --no-cache \
git \
curl \
netcat-openbsd \
&& rm -rf /var/cache/apk/*
WORKDIR /app
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
# Create an Expo project with the default template
RUN npx create-expo-app@latest .
# Install the dependencies required for Expo web support
RUN npx expo install react-dom react-native-web
# Pre-warm the Metro bundler cache using the development server
RUN timeout 120 npx expo start --web --port 8081 --no-dev-client 2>/dev/null || true
ENV PATH="/usr/local/bin:$PATH"
ENTRYPOINT ["/usr/local/bin/sandbox-api"]
```
## 3. Configure and deploy the image
Create `blaxel.toml` beside the `Dockerfile`:
```toml theme={null}
type = "sandbox"
name = "expo-template"
[runtime]
memory = 8192
[[runtime.ports]]
name = "expo-web"
target = 8081
```
Deploy the image:
```bash theme={null}
bl deploy
```
## 4. Create or reuse a sandbox
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-expo-sandbox";
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "expo",
},
image: "expo-template:latest",
memory: 8192,
ports: [
{ name: "preview", target: 8081, protocol: "HTTP" },
],
});
```
## 5. Configure preview response headers
Expo development servers require permissive CORS headers when accessed through a preview URL:
```typescript theme={null}
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
```
Alternatively, use [custom domains](../../Infrastructure/Custom-domains) to expose previews on your own domain.
## 6. Create the preview and token
Expo runs on port `8081`. Create a private preview for that port:
```typescript theme={null}
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "dev-server-preview" },
spec: {
responseHeaders,
public: false,
port: 8081,
},
});
```
Generate a token that expires after one day:
```typescript theme={null}
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
const token = await preview.tokens.create(expiresAt);
```
## 7. Inject the preview URL into Expo
Expo Router requires the correct origin when it runs behind a proxy. Update `app.json` in the sandbox:
```typescript theme={null}
async function addRouterOriginToAppJson(
sandbox: SandboxInstance,
previewUrl: string
) {
const appJsonPath = "/app/app.json";
const appJsonContent = await sandbox.fs.read(appJsonPath);
const appJson = JSON.parse(appJsonContent);
appJson.expo = {
...appJson.expo,
extra: {
...(appJson.expo.extra || {}),
router: {
...(appJson.expo.extra?.router || {}),
origin: previewUrl,
},
},
};
await sandbox.fs.write(appJsonPath, JSON.stringify(appJson, null, 2));
}
```
## 8. Configure the proxy URL
Expo must serve assets through the preview URL. The following function updates `.env` only when the expected value is missing:
```typescript theme={null}
async function configureExpoProxyUrl(
sandbox: SandboxInstance,
previewUrl: string
): Promise {
const baseUrl = previewUrl.replace(/\/$/, "");
let envContent = "";
try {
envContent = await sandbox.fs.read("/app/.env");
} catch {
// The file does not exist yet
}
const expectedEnvLine = `EXPO_PACKAGER_PROXY_URL=${baseUrl}`;
if (envContent.includes(expectedEnvLine)) {
console.log("Expo proxy URL already configured correctly");
return false;
}
const lines = envContent
.split("\n")
.filter((line) => !line.startsWith("EXPO_PACKAGER_PROXY_URL="));
lines.push(expectedEnvLine);
await sandbox.fs.write("/app/.env", lines.join("\n"));
console.log(`Configured Expo to use proxy URL: ${baseUrl}`);
return true;
}
```
## 9. Start and monitor the development server
After setting the proxy URL, start the development server:
```typescript theme={null}
async function startDevServer(sandbox: SandboxInstance) {
console.log("Starting dev server with updated configuration...");
await sandbox.process.exec({
name: "dev-server",
command: "npx expo start --web --port 8081 --scheme exp",
workingDir: "/app",
waitForPorts: [8081],
restartOnFailure: true,
maxRestarts: 25,
});
}
```
Stream its logs:
```typescript theme={null}
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log);
},
});
logStream.close();
```
## 10. Access the Expo application
Open the web application at:
```text theme={null}
${preview.spec?.url}?bl_preview_token=${token.value}
```
To access the application on a mobile device, build an Expo URL from the authenticated preview URL:
```typescript theme={null}
const previewUrl = `${preview.spec?.url}?bl_preview_token=${token.value}`;
const expoUrl = previewUrl.replace("https://", "exp://");
```
Encode the `exp://` URL in a QR code:
```text theme={null}
exp://your-sandbox.preview.blaxel.ai/?bl_preview_token=your-token
```
Scan the QR code with your device's camera or the Expo Go application.
## 11. Run the complete example
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-expo-sandbox";
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
async function addRouterOriginToAppJson(
sandbox: SandboxInstance,
previewUrl: string
) {
const appJsonPath = "/app/app.json";
const appJsonContent = await sandbox.fs.read(appJsonPath);
const appJson = JSON.parse(appJsonContent);
appJson.expo = {
...appJson.expo,
extra: {
...(appJson.expo.extra || {}),
router: {
...(appJson.expo.extra?.router || {}),
origin: previewUrl,
},
},
};
await sandbox.fs.write(appJsonPath, JSON.stringify(appJson, null, 2));
}
async function configureExpoProxyUrl(
sandbox: SandboxInstance,
previewUrl: string
): Promise {
const baseUrl = previewUrl.replace(/\/$/, "");
let envContent = "";
try {
envContent = await sandbox.fs.read("/app/.env");
} catch {
// The file does not exist
}
const expectedEnvLine = `EXPO_PACKAGER_PROXY_URL=${baseUrl}`;
if (envContent.includes(expectedEnvLine)) {
return false;
}
const lines = envContent
.split("\n")
.filter((line) => !line.startsWith("EXPO_PACKAGER_PROXY_URL="));
lines.push(expectedEnvLine);
await sandbox.fs.write("/app/.env", lines.join("\n"));
return true;
}
async function startDevServer(sandbox: SandboxInstance) {
await sandbox.process.exec({
name: "dev-server",
command: "npx expo start --web --port 8081 --scheme exp",
workingDir: "/app",
waitForPorts: [8081],
restartOnFailure: true,
maxRestarts: 25,
});
}
async function configureExpo(
sandbox: SandboxInstance,
previewUrl: string
) {
await addRouterOriginToAppJson(sandbox, previewUrl);
await configureExpoProxyUrl(sandbox, previewUrl);
await startDevServer(sandbox);
}
async function main() {
try {
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "expo",
},
image: "expo-template:latest",
memory: 8192,
ports: [
{ name: "preview", target: 8081, protocol: "HTTP" },
],
});
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "preview" },
spec: {
responseHeaders,
public: false,
port: 8081,
},
});
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
const token = await preview.tokens.create(expiresAt);
await configureExpo(sandbox, preview.spec?.url!);
const processes = await sandbox.process.list();
if (!processes.find((process) => process.name === "dev-server")) {
await startDevServer(sandbox);
}
const webUrl = `${preview.spec?.url}?bl_preview_token=${token.value}`;
const expoUrl = webUrl.replace("https://", "exp://");
console.log(`Web Preview URL: ${webUrl}`);
console.log(`Expo Mobile URL: ${expoUrl}`);
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log);
},
});
process.on("SIGINT", () => {
logStream.close();
process.exit(0);
});
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();
```
This setup runs Expo fully inside a Blaxel Sandbox, securely exposes the development server, supports Expo Router and asset loading, provides mobile access through the `exp://` protocol, and starts the server after configuration changes. It is suitable for preview environments, internal demos, and AI-powered coding workflows.
## Resources
Configure public and private preview URLs for sandbox services.
Build and manage reusable Blaxel Sandbox images.
# Set up Next.js with Blaxel Sandboxes
Source: https://docs.blaxel.ai/knowledge-base/integrations/setting-up-nextjs-with-blaxel-sandboxes
Run a Next.js application in a Blaxel Sandbox and expose it securely with a Blaxel preview URL.
This guide explains how to run a Next.js application inside a Blaxel Sandbox and expose it securely using Blaxel previews.
## Prerequisites
* [Blaxel CLI](../../cli-reference/introduction) installed and authenticated with `bl login`
* Node.js 18 or later
* `@blaxel/core` installed in your project with `npm install @blaxel/core`
## 1. Understand the architecture
Running Next.js inside a Blaxel Sandbox requires minimal adjustments compared to local development:
* Next.js binds to all interfaces by default in development mode
* Blaxel exposes services through preview URLs
* The preview URL handles routing and authentication
Run the Next.js development server on port `3000`, then expose it through a [Blaxel preview URL](../../Sandboxes/Preview-url).
## 2. Build the sandbox image
Create a `Dockerfile`:
```dockerfile theme={null}
FROM node:22-alpine
RUN apk update && apk add --no-cache \
git \
curl \
netcat-openbsd \
&& rm -rf /var/cache/apk/*
WORKDIR /app
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
# Create a Next.js project with TypeScript, Tailwind, App Router, and Turbopack
RUN mkdir -p /app \
&& npx create-next-app@latest /app --use-npm --typescript --eslint --tailwind --src-dir --app --turbopack \
&& cd /app && npm install --save @next/swc-linux-x64-musl
COPY ./next.config.ts /app/next.config.ts
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENV PATH="/usr/local/bin:$PATH"
ENTRYPOINT ["/entrypoint.sh"]
```
Create an `entrypoint.sh` script that starts the sandbox API and development server:
```bash theme={null}
#!/bin/sh
export PATH="/usr/local/bin:$PATH"
/usr/local/bin/sandbox-api &
wait_for_port() {
local port=$1
local timeout=30
local count=0
echo "Waiting for port $port to be available..."
while ! nc -z localhost "$port"; do
sleep 1
count=$((count + 1))
if [ "$count" -gt "$timeout" ]; then
echo "Timeout waiting for port $port"
exit 1
fi
done
echo "Port $port is now available"
}
wait_for_port 8080
echo "Running Next.js dev server..."
curl http://localhost:8080/process \
-X POST \
-H "Content-Type: application/json" \
-d '{
"name": "dev-server",
"workingDir": "/app",
"command": "npm run dev -- --port 3000",
"waitForCompletion": false,
"restartOnFailure": true,
"maxRestarts": 25
}'
wait
```
## 3. Configure Next.js
Create `next.config.ts`:
```typescript theme={null}
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
allowedDevOrigins: ["*.preview.bl.run"],
};
export default nextConfig;
```
The `allowedDevOrigins` setting lets the development server accept requests from Blaxel preview origins. If your Blaxel workspace uses a custom domain, add it to the array:
```typescript theme={null}
allowedDevOrigins: ["*.preview.bl.run", "*.preview.mycompany.com"];
```
## 4. Configure and deploy the image
Create `blaxel.toml` in the same directory as your `Dockerfile`:
```toml theme={null}
type = "sandbox"
name = "nextjs-template"
[runtime]
memory = 4096
[[runtime.ports]]
name = "nextjs-dev"
target = 3000
protocol = "tcp"
```
Deploy the image:
```bash theme={null}
bl deploy
```
For more information about reusable images, see [sandbox images](../../Sandboxes/Templates).
## 5. Create or reuse a sandbox
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-nextjs-sandbox";
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "nextjs",
},
image: "nextjs-template:latest",
memory: 4096,
ports: [
{ name: "preview", target: 3000, protocol: "HTTP" },
],
});
```
## 6. Configure preview response headers
Next.js development servers work well with permissive CORS headers when accessed through a preview URL:
```typescript theme={null}
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
```
Alternatively, use [custom domains](../../Infrastructure/Custom-domains) to expose previews on your own domain.
## 7. Create the preview and token
Next.js runs on port `3000`. Create a private preview for that port:
```typescript theme={null}
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "dev-server-preview" },
spec: {
responseHeaders,
public: false,
port: 3000,
},
});
```
Generate a token that expires after one day:
```typescript theme={null}
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
const token = await preview.tokens.create(expiresAt);
```
## 8. Start and monitor the development server
If you do not use the entrypoint script, start the development server programmatically:
```typescript theme={null}
async function startDevServer(sandbox: SandboxInstance) {
console.log("Starting Next.js dev server...");
await sandbox.process.exec({
name: "dev-server",
command: "npm run dev -- --port 3000",
workingDir: "/app",
waitForPorts: [3000],
restartOnFailure: true,
maxRestarts: 25,
});
}
```
Stream its logs:
```typescript theme={null}
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log);
},
});
logStream.close();
```
## 9. Access the Next.js application
Once the server is running, open:
```text theme={null}
${preview.spec?.url}?bl_preview_token=${token.value}
```
## 10. Run the complete example
The following example creates the sandbox and preview, starts the server when needed, prints the access URL, and streams logs:
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-nextjs-sandbox";
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
async function startDevServer(sandbox: SandboxInstance) {
await sandbox.process.exec({
name: "dev-server",
command: "npm run dev -- --port 3000",
workingDir: "/app",
waitForPorts: [3000],
restartOnFailure: true,
maxRestarts: 25,
});
}
async function main() {
try {
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "nextjs",
},
image: "nextjs-template:latest",
memory: 4096,
ports: [
{ name: "preview", target: 3000, protocol: "HTTP" },
],
});
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "preview" },
spec: {
responseHeaders,
public: false,
port: 3000,
},
});
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
const token = await preview.tokens.create(expiresAt);
const processes = await sandbox.process.list();
if (!processes.find((process) => process.name === "dev-server")) {
await startDevServer(sandbox);
}
const webUrl = `${preview.spec?.url}?bl_preview_token=${token.value}`;
console.log(`Next.js Preview URL: ${webUrl}`);
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log);
},
});
process.on("SIGINT", () => {
logStream.close();
process.exit(0);
});
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();
```
## 11. Use Next.js features
### Turbopack
The template uses Turbopack, Next.js's Rust-based bundler. It provides:
* Faster cold starts
* Instant hot module replacement
* Optimized incremental compilation
The `@next/swc-linux-x64-musl` package is preinstalled for Alpine Linux performance.
### App Router
The template configures the App Router with the `/app` directory structure. It provides:
* Server Components by default
* Nested layouts
* Loading and error states
* Server Actions
### TypeScript, Tailwind CSS, and ESLint
The template enables TypeScript with strict type checking. Tailwind CSS is preconfigured in `src/app/globals.css`, and ESLint uses the recommended Next.js rules.
This setup runs Next.js fully inside a Blaxel Sandbox, securely exposes the development server, supports Fast Refresh, and provides a fast environment for previews, internal demos, and AI-powered coding workflows.
## Resources
Configure public and private preview URLs for sandbox services.
Build and manage reusable Blaxel Sandbox images.
# Set up Vite with Blaxel Sandboxes
Source: https://docs.blaxel.ai/knowledge-base/integrations/setting-up-vite-with-blaxel-sandboxes
Run a Vite React and TypeScript application in a Blaxel Sandbox and expose it securely with a preview URL.
This guide explains how to run a Vite React and TypeScript application inside a Blaxel Sandbox and expose it securely using Blaxel previews.
## Prerequisites
* [Blaxel CLI](../../cli-reference/introduction) installed and authenticated with `bl login`
* Node.js 18 or later
* `@blaxel/core` installed in your project with `npm install @blaxel/core`
## 1. Understand the architecture
Running Vite inside a Blaxel Sandbox requires a few adjustments:
* Vite normally binds to localhost
* Blaxel exposes services through preview URLs
* Vite must accept external connections and all hosts
Configure `vite.config.ts` with `host: "0.0.0.0"` and `allowedHosts: true`, then expose the Vite development server through a [Blaxel preview URL](../../Sandboxes/Preview-url).
## 2. Build the sandbox image
Create a `Dockerfile`:
```dockerfile theme={null}
FROM node:22-alpine
RUN apk update && apk add --no-cache \
git curl bash \
&& rm -rf /var/cache/apk/*
WORKDIR /app
RUN curl -fsSL https://bun.sh/install | bash \
&& ln -s /root/.bun/bin/bun /usr/local/bin/bun \
&& ln -s /root/.bun/bin/bunx /usr/local/bin/bunx
RUN bun create vite@latest . --template react-ts
# Install patch-package globally first to avoid postinstall script errors
RUN bun install -g patch-package
RUN bun install
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
COPY ./vite.config.ts /app/vite.config.ts
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
```
## 3. Configure Vite
Create `vite.config.ts` to allow external connections:
```typescript theme={null}
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
port: 5173,
host: "0.0.0.0",
allowedHosts: true,
},
});
```
## 4. Create the entrypoint
Create `entrypoint.sh` to start the sandbox API and Vite development server:
```bash theme={null}
#!/bin/sh
export PATH="/usr/local/bin:$PATH"
/usr/local/bin/sandbox-api &
wait_for_port() {
local port=$1
local timeout=30
local count=0
echo "Waiting for port $port to be available..."
while ! nc -z localhost "$port"; do
sleep 1
count=$((count + 1))
if [ "$count" -gt "$timeout" ]; then
echo "Timeout waiting for port $port"
exit 1
fi
done
echo "Port $port is now available"
}
wait_for_port 8080
echo "Running Vite dev server with Bun..."
curl http://localhost:8080/process \
-X POST \
-H "Content-Type: application/json" \
-d '{
"name": "dev-server",
"workingDir": "/app",
"command": "bun run dev",
"waitForCompletion": false,
"restartOnFailure": true,
"maxRestarts": 25
}'
wait
```
## 5. Configure and deploy the image
Create `blaxel.toml` beside the `Dockerfile`:
```toml theme={null}
type = "sandbox"
name = "vite-template"
[runtime]
memory = 4096
[[runtime.ports]]
name = "vite-dev"
target = 5173
protocol = "tcp"
```
Deploy the image:
```bash theme={null}
bl deploy
```
## 6. Create or reuse a sandbox
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-vite-sandbox";
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "vite",
},
image: "vite-template:latest",
memory: 4096,
ports: [
{ name: "preview", target: 5173, protocol: "HTTP" },
],
});
```
## 7. Configure preview response headers
Vite development servers work well with permissive CORS headers when accessed through a preview URL:
```typescript theme={null}
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
```
Alternatively, use [custom domains](../../Infrastructure/Custom-domains) to expose previews on your own domain.
## 8. Create the preview and token
Vite runs on port `5173`. Create a private preview for that port:
```typescript theme={null}
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "dev-server-preview" },
spec: {
responseHeaders,
public: false,
port: 5173,
},
});
```
Generate a token that expires after one day:
```typescript theme={null}
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
const token = await preview.tokens.create(expiresAt);
```
## 9. Start and monitor the development server
If you do not use the entrypoint script, start Vite programmatically:
```typescript theme={null}
async function startDevServer(sandbox: SandboxInstance) {
console.log("Starting Vite dev server...");
await sandbox.process.exec({
name: "dev-server",
command: "bun run dev",
workingDir: "/app",
waitForPorts: [5173],
restartOnFailure: true,
maxRestarts: 25,
});
}
```
Stream its logs:
```typescript theme={null}
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log);
},
});
logStream.close();
```
## 10. Access the Vite application
Once the server is running, open:
```text theme={null}
${preview.spec?.url}?bl_preview_token=${token.value}
```
## 11. Run the complete example
```typescript theme={null}
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-vite-sandbox";
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
async function startDevServer(sandbox: SandboxInstance) {
await sandbox.process.exec({
name: "dev-server",
command: "bun run dev",
workingDir: "/app",
waitForPorts: [5173],
restartOnFailure: true,
maxRestarts: 25,
});
}
async function main() {
try {
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "vite",
},
image: "vite-template:latest",
memory: 4096,
ports: [
{ name: "preview", target: 5173, protocol: "HTTP" },
],
});
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "preview" },
spec: {
responseHeaders,
public: false,
port: 5173,
},
});
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
const token = await preview.tokens.create(expiresAt);
const processes = await sandbox.process.list();
if (!processes.find((process) => process.name === "dev-server")) {
await startDevServer(sandbox);
}
const webUrl = `${preview.spec?.url}?bl_preview_token=${token.value}`;
console.log(`Vite Preview URL: ${webUrl}`);
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log);
},
});
process.on("SIGINT", () => {
logStream.close();
process.exit(0);
});
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();
```
This setup runs Vite fully inside a Blaxel Sandbox, securely exposes the development server, supports hot module replacement, and uses Bun for fast dependency installation and development server performance. It is suitable for preview environments, internal demos, and AI-powered coding workflows.
## Resources
Configure public and private preview URLs for sandbox services.
Build and manage reusable Blaxel Sandbox images.
# Knowledge Base
Source: https://docs.blaxel.ai/knowledge-base/overview
Find practical answers, setup guides, security guidance, and troubleshooting help for Blaxel sandboxes and platform workflows.
Browse answers and guides migrated from the Blaxel support knowledge base.
Set up popular application frameworks in Blaxel sandboxes.
Configure sandbox networking, lifecycle, storage, images, and previews.
Authenticate Blaxel tools and meet identity and compliance requirements.
Resolve common sandbox creation, networking, preview, and storage errors.
# How to authenticate the Blaxel CLI in CI environments
Source: https://docs.blaxel.ai/knowledge-base/security-and-authentication/how-to-authenticate-the-bl-cli-in-ci-environments
Authenticate the Blaxel CLI with a dedicated API key to automate deployments and other tasks in CI/CD environments.
To use the Blaxel CLI in CI/CD environments for tasks such as pushing sandbox images, authenticate with an API key.
## GitHub Action
The recommended way to integrate the Blaxel CLI into a GitHub CI pipeline is the official [Blaxel GitHub Action](https://github.com/blaxel-ai/bl-action).
## Manual authentication
For another CI platform or a manual setup:
1. Create a dedicated workspace API key in the [Blaxel Console](https://app.blaxel.ai/profile/security).
2. Set the API key as an environment variable and log in:
```bash theme={null}
export BL_API_KEY=your-api-key-here
bl login your-workspace
```
3. After login succeeds, run any Blaxel CLI command in your CI environment.
Use a dedicated workspace API key for CI/CD automation instead of your personal API key.
## Resources
Learn how to create and manage API keys for the CLI and CI environments.
Install the CLI and review its available commands.
# Multi-Factor Authentication (MFA) and compliance requirements
Source: https://docs.blaxel.ai/knowledge-base/security-and-authentication/multi-factor-authentication-mfa-and-compliance-requirements
Meet MFA and compliance requirements on Blaxel by enforcing MFA through your identity provider and using SAML SSO and Directory Sync.
Blaxel does not currently provide a built-in multi-factor authentication (MFA) mechanism. Enforce MFA through your external authentication provider instead.
## MFA support
Connect your identity provider, such as Okta, Azure AD, Google Workspace, or OneLogin, and enforce MFA at the identity provider level.
When SAML is active and enforced for your account, it becomes the only allowed login method for your domain. See [SAML and Directory Sync](/Security/SSO-Directory-sync) for configuration details.
## Compliance considerations
Use these controls to support HIPAA and SOC 2 compliance requirements:
* SAML SSO with identity provider-enforced MFA is the strongest option and is recommended for enterprise compliance
* Directory Sync (SCIM) automates user provisioning and deprovisioning based on your identity provider directory, supporting access lifecycle management requirements
* OAuth providers such as Google or GitHub satisfy most MFA requirements when MFA is enabled at the provider
SOC 2 and HIPAA audit reports are available upon request.
## Resources
Configure SAML SSO and automated provisioning through SCIM.
Request compliance reports or help with enterprise authentication.
# Setting up MCP servers with authentication on Blaxel
Source: https://docs.blaxel.ai/knowledge-base/security-and-authentication/setting-up-mcp-servers-with-authentication-on-blaxel
Authenticate connections to Blaxel-hosted MCP servers with API keys or short-lived OAuth tokens.
Blaxel supports connections to Blaxel-hosted Model Context Protocol (MCP) servers using Blaxel API keys or OAuth tokens. MCP servers deployed on Blaxel are not public by default, so every connection must be authenticated.
## Authentication methods
### API key authentication
Create long-lived API keys in the Blaxel Console. Use an API key directly as a bearer token:
```http theme={null}
Authorization: Bearer bl_xxxxxxxx
```
### OAuth authentication
Short-lived OAuth tokens are valid for two hours and provide a more secure option. Obtain them through any of these methods:
* CLI: Run `bl login` to authenticate through the Blaxel Console
* Service accounts: Use the client credentials grant type with `/oauth/token`
* Full OAuth 2.0 flows: Blaxel implements all OAuth 2.0 grant types
The well-known OpenID configuration is available at:
```text theme={null}
https://api.blaxel.ai/v0/.well-known/openid-configuration
```
See [Access tokens](/Security/Access-tokens) for more information about API keys, OAuth tokens, and service accounts.
## Public access
Make an MCP server public through `blaxel.toml` to bypass Blaxel authentication:
```toml theme={null}
public = true
```
When you make the server public, you must implement your own authentication.
## Current implementation
For more detailed information about MCP authentication patterns, refer to Cloudflare's article on building AI agents with MCP authentication.
## Connect to the server
Choose a long-lived API key or a short-lived OAuth token.
* For API keys, create a key in the Blaxel Console under Profile > Security or through a service account
* For OAuth, run `bl login` for CLI access or use service account client credentials with `/oauth/token`
* Pass the token as `Authorization: Bearer ` when connecting to the MCP server
* For accounts with multiple workspaces, optionally pass `X-Blaxel-Workspace: `
## Resources
Learn how to create and use Blaxel API keys and OAuth tokens.
Develop and configure a custom MCP server on Blaxel.
# Handling Preview not found 404 errors in sandboxes
Source: https://docs.blaxel.ai/knowledge-base/troubleshooting/handling-preview-not-found-404-errors-in-sandboxes
Resolve Preview not found 404 errors by checking sandbox and preview lifecycle state, TTL settings, and asynchronous deletion races.
A `Preview not found` response with a `404` status means the requested preview has been deleted or is being deleted. Check the lifecycle state of both the parent sandbox and the preview.
## Common causes
This error occurs when:
* The parent sandbox was deleted manually
* The parent sandbox expired because of its TTL
* The preview's TTL expired
* An asynchronous deletion request placed the preview in the `DELETING` state
## Resolve the error
Create previews with `createIfNotExists` in TypeScript or `create_if_not_exists` in Python. When a preview is in the `DELETING` state, the platform waits for deletion to finish before recreating it. These methods safely reuse preview IDs and avoid a race with deletion.
Also verify that the sandbox and preview are still active and were not removed by TTL expiration or manual cleanup.
## Prevent future errors
* Handle `404` responses gracefully in your application
* Poll the sandbox status periodically where appropriate
* Configure sandbox TTL values that match your usage pattern
If the error continues after these checks, contact support.
Learn how to create and use sandbox previews.
Review sandbox lifecycle and configuration.
# Fix Miniflare inspector startup failures on Carbon sandboxes
Source: https://docs.blaxel.ai/knowledge-base/troubleshooting/miniflare-inspector-startup-failures-on-carbon
Diagnose Astro and Cloudflare preview startup failures caused by older Miniflare versions selecting an unavailable IPv6 localhost address.
An Astro or Cloudflare preview can fail to start in a [Carbon (Mark 3.1)](/Infrastructure/Gens#carbon-aka-mark-31) sandbox when an older Miniflare version binds its runtime inspector to `localhost`. Upgrade the Cloudflare development dependencies or disable the inspector when you do not need it.
## Symptoms
The preview process may hang at:
```text theme={null}
Starting local server...
```
In applications that wait for the preview server, the visible error can instead be a secondary connection failure:
```text theme={null}
fetch failed
Caused by:
connect ECONNREFUSED 127.0.0.1:
```
The refused connection does not necessarily mean that the application selected the wrong port. It can mean that Miniflare never finished starting the underlying `workerd` runtime.
## Confirm the sandbox generation
Run the following command inside the sandbox:
```bash theme={null}
ip addr show
```
Inspect the main non-loopback interface. A global `inet6` address indicates that the sandbox is running on [Carbon (Mark 3.1)](/Infrastructure/Gens#carbon-aka-mark-31).
The presence of IPv6 identifies the sandbox generation during this rollout. It does not by itself prove that Miniflare is affected.
## Check for the Miniflare issue
Check which Cloudflare packages the application resolved:
```bash theme={null}
npm ls miniflare wrangler @cloudflare/vite-plugin @astrojs/cloudflare
```
Then inspect localhost resolution and the IPv6 loopback interface:
```bash theme={null}
getent hosts localhost
ip -6 addr show dev lo
sysctl net.ipv6.conf.lo.disable_ipv6
```
The known failure occurs when `localhost` resolves to `::1`, but the runtime cannot bind the IPv6 loopback address. Older Miniflare versions pass `--inspector-addr=localhost:0` to `workerd` and wait for an inspector-ready event that never arrives.
Confirm the inspector address while the preview process is stuck:
```bash theme={null}
ps -ef | grep '[w]orkerd'
```
An affected process includes:
```text theme={null}
--inspector-addr=localhost:0
```
## Upgrade Miniflare or Wrangler
The preferred fix is to update the package that provides Miniflare and regenerate the lockfile. The fix is included in:
* Miniflare `4.20260601.0` and later
* Wrangler `4.97.0` and later
For a project that uses Wrangler directly:
```bash theme={null}
npm install --save-dev wrangler@latest
```
Run `npm ls miniflare wrangler` again to verify the resolved versions. The fixed Miniflare release uses `127.0.0.1` for the runtime inspector instead of relying on `localhost` resolution.
## Disable the inspector
If you cannot upgrade immediately and do not need runtime debugging, disable the inspector in the Cloudflare integration.
For an Astro project whose Cloudflare adapter supports `inspectorPort`, update `astro.config.mjs`:
```javascript theme={null}
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
export default defineConfig({
adapter: cloudflare({
inspectorPort: false,
}),
});
```
For a project that configures the Cloudflare Vite plugin directly:
```javascript theme={null}
import { cloudflare } from "@cloudflare/vite-plugin";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
cloudflare({
inspectorPort: false,
}),
],
});
```
Restart the preview process after changing the configuration. This workaround disables local runtime debugging but does not affect the application preview.
## Related Cloudflare fix
Cloudflare fixed the underlying address selection in [workers-sdk pull request #14086](https://github.com/cloudflare/workers-sdk/pull/14086). The change replaces `localhost` with `127.0.0.1` for the runtime inspector address.
Configure and expose an Astro development server in a sandbox.
Compare Carbon with earlier Blaxel infrastructure generations.
# Resolving ENOSPC no space left on device errors
Source: https://docs.blaxel.ai/knowledge-base/troubleshooting/resolve-enospc-no-space-left-on-device-errors
Resolve ENOSPC storage errors in Blaxel sandboxes by increasing memory allocation or attaching a volume for larger persistent storage.
An `ENOSPC: no space left on device` error means that your sandbox has exhausted its available storage. Sandbox filesystem storage is in memory by default for optimal speed, so storage needs count against the memory allocation.
## Increase memory allocation
A sandbox's memory allocation covers both runtime memory and filesystem storage. Increase the allocation to provide more storage space. This is the recommended short-term solution.
If the sandbox logs contain `Out of memory: Killed process`, the sandbox exceeded its memory allocation and needs more memory.
Increasing memory also increases CPU allocation:
* 8 GB of memory provides 4 CPU cores
* 16 GB of memory provides 8 CPU cores
## Use a volume
If files alone require more than 4 GB, use a volume for larger, persistent storage. A volume may provide slower filesystem performance than the default in-memory storage.
Add persistent storage to a sandbox.
Learn how files are stored and managed in sandboxes.
# Resolving sandbox creation issues
Source: https://docs.blaxel.ai/knowledge-base/troubleshooting/resolving-sandbox-creation-issues
Fix Blaxel sandboxes that fail during startup because their custom image has a missing or incorrect entrypoint.
If your sandbox is created but fails to start, its template may have a missing or incorrect entrypoint. The entrypoint must keep the sandbox API running.
## Common symptoms
* The sandbox appears to be created but immediately fails or is deleted
* Requests to the sandbox return a `404 Not Found` error
## Root cause
The template does not have a functional entrypoint that keeps the sandbox API running. Even when your container runs commands on demand, the sandbox API must remain active for the sandbox to work.
## Configure the entrypoint
Always copy the sandbox API binary into your custom image:
```dockerfile theme={null}
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
```
Do not leave the entrypoint empty. If you do not need custom initialization, start the sandbox API directly:
```dockerfile theme={null}
ENTRYPOINT ["/usr/local/bin/sandbox-api"]
```
If you need initialization logic, use a custom entrypoint script instead of a Dockerfile `RUN` instruction:
```dockerfile theme={null}
ENTRYPOINT ["/entrypoint.sh"]
```
The script must start `sandbox-api`, wait for it to become available, and then start other processes through the `/process` API. End the script with `wait` so the container remains alive.
## Additional considerations
For templates designed to run commands on demand:
* Keep a process running to maintain the sandbox API connection
* Run commands with `sandbox.process.exec` while the entrypoint remains active
* Remember that a sandbox snapshots its entire VM state, including memory, filesystem data, and running processes, when it enters standby
* Expect the sandbox to restore from standby in under 25 ms
After updating the entrypoint, rebuild the template and create a new sandbox.
Learn how Blaxel sandboxes run and manage workloads.
Learn how to execute and manage sandbox processes.
# Resolving sandbox creation race conditions and duplicate requests
Source: https://docs.blaxel.ai/knowledge-base/troubleshooting/resolving-sandbox-creation-race-conditions-and-duplicate-requests
Prevent duplicate Blaxel sandbox creation requests and handle 409 conflicts safely with idempotent SDK methods.
Sandbox creation can fail when your application sends multiple requests to create the same sandbox at the same time. Use an idempotent creation method and add client-side request guards where needed.
## Common errors
You may receive either of these errors:
* `SANDBOX_ALREADY_EXISTS (409)`
* `RATE_LIMIT_EXCEEDED (429)`
## Root cause
Duplicate creation requests commonly result from:
* React Strict Mode intentionally running effects twice in development
* React hooks running again during component re-renders
* Rapid user interactions triggering the same API call
Blaxel has improved platform-side race condition handling, which prevents most failures even when duplicate requests occur.
## Use idempotent creation
Use `createIfNotExists()` in TypeScript or `create_if_not_exists()` in Python instead of `create()`. These methods handle a `409` conflict by returning the existing sandbox.
```typescript TypeScript theme={null}
const sandbox = await SandboxInstance.createIfNotExists({
name: "my-sandbox",
image: "blaxel/base-image:latest",
memory: 4096,
region: "us-pdx-1",
});
```
```python Python theme={null}
sandbox = await SandboxInstance.create_if_not_exists({
"name": "my-sandbox",
"image": "blaxel/base-image:latest",
"memory": 4096,
"region": "us-pdx-1",
})
```
## Add request guards
As an optional client-side safeguard:
* Track an in-progress request with an `isCreating` flag
* Check whether creation is already in progress before sending another request
* Show an appropriate loading state while creation completes
## Handle React Strict Mode
In React development environments:
* Cancel duplicate requests in the `useEffect` cleanup function
* Define accurate dependency arrays to avoid unnecessary effect runs
* Track request state across re-renders with `useRef`
## Investigate persistent failures
If race conditions continue after you apply these changes:
1. Record the sandbox ID and error timestamp.
2. Check whether your application sends duplicate creation calls.
3. Contact support with the error details.
Learn how to create and manage Blaxel sandboxes.
Review recommended patterns for reliable sandbox workloads.
# Sandbox limits and quota exceeded errors
Source: https://docs.blaxel.ai/knowledge-base/troubleshooting/sandbox-limits-and-quota-exceeded-errors
Resolve QUOTA_EXCEEDED errors by checking your Blaxel account tier, memory allocation, payment method, and sandbox quota.
If sandbox creation returns `QUOTA_EXCEEDED`, your workload may exceed the limits for your account tier. Memory limits and allowed sandbox counts vary by tier.
## Resolve quota errors
If you need more than your tier's standard limits, top up credits to upgrade your tier or request a quota increase in the Console. Tier 0 users must add a payment method and upgrade to Tier 1 before requesting an increase.
In exceptional cases, contact support. Blaxel may manually increase quota limits for larger workloads where necessary.
## Troubleshoot your quota
1. Verify your account tier and payment method in your account settings.
2. Reduce the sandbox memory allocation temporarily and retry the request.
3. Review the official quota documentation.
4. Contact support with the exact error and your memory requirements if the problem continues.
Review account tiers and resource quota limits.
Learn how sandbox resources are configured.
# Resolving sandbox restarts caused by nc not found
Source: https://docs.blaxel.ai/knowledge-base/troubleshooting/sandbox-restarting-nc-not-found
Stop sandbox restart loops by removing an unnecessary custom entrypoint or installing netcat and using the loopback IP in Debian-based images.
A sandbox can restart repeatedly when its `entrypoint.sh` script cannot start the sandbox API. The usual causes are a missing netcat package or failed `localhost` resolution in a Debian-based image.
## Remove the entrypoint script
If you do not need custom initialization, remove `entrypoint.sh` and start the sandbox API directly:
```dockerfile theme={null}
ENTRYPOINT ["/usr/local/bin/sandbox-api"]
```
Remove the custom entrypoint instructions:
```dockerfile theme={null}
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
```
## Fix the entrypoint script
If you need custom initialization, install `netcat-openbsd` in the Dockerfile:
```dockerfile theme={null}
RUN apt-get update \
&& apt-get install --assume-yes --no-install-recommends --quiet \
netcat-openbsd \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
```
Then replace `localhost` with `127.0.0.1` in the port check:
```sh theme={null}
while ! nc -z 127.0.0.1 "$port"; do
sleep 1
done
```
Debian-based images do not always resolve `localhost` automatically. Use `127.0.0.1` for local connections in these images.
## Inspect sandbox logs
Check the console logs for errors from the entrypoint script:
```bash theme={null}
bl logs sbx your-sandbox-name
```
Learn how to start and manage sandbox processes.
Learn how to configure Docker workloads in sandboxes.
# SSL certificate verification errors when cloning repositories
Source: https://docs.blaxel.ai/knowledge-base/troubleshooting/ssl-certificate-verification-errors-when-cloning-repositories
Fix Git SSL certificate verification failures in Blaxel sandboxes by installing CA certificates or choosing a base image that includes them.
Cloning a repository in a Blaxel sandbox can fail with an SSL certificate verification error. Minimal base images often omit the certificate authorities required to verify HTTPS connections.
## Example error
The Git error resembles:
```text theme={null}
fatal: unable to access 'https://github.com/blaxel-ai/sdk-typescript.git': server certificate verification failed
```
## Root cause
Slim base images such as `node:22-bookworm-slim` do not always include CA certificates. Without a trusted certificate authority bundle, Git cannot verify the remote server's SSL certificate.
## Install CA certificates
Add `ca-certificates` to the packages installed by your Dockerfile:
```dockerfile theme={null}
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
```
## Choose another base image
Alternatively, use a base image that includes CA certificates:
* `node:22-alpine`
* `node:22-bookworm`, using the full image instead of the slim variant
The Alpine variant has been tested successfully with repository cloning. If you switch to Alpine, update your package installation commands because Alpine uses `apk` instead of `apt-get`, and package names or availability may differ.
Learn how to build and run Docker images in sandboxes.
Review sandbox networking configuration.
# Troubleshooting custom Docker image deployment issues
Source: https://docs.blaxel.ai/knowledge-base/troubleshooting/troubleshooting-custom-docker-image-deployment-issues
Diagnose sandbox API, Dockerfile path, build timeout, entrypoint, missing build tools, and logging issues in custom Blaxel sandbox images.
Custom Docker images for sandboxes can fail during build or deployment because of missing files, dependencies, or startup configuration. Use the matching section below to identify and resolve the failure.
## Include the sandbox API
Every custom sandbox image must include the `sandbox-api` binary:
```dockerfile theme={null}
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
```
## Correct Dockerfile paths
When you run `bl deploy`, Blaxel archives all files in your current directory and sends them to the build server. If the Dockerfile is in a subdirectory such as `agent-container/`, write `COPY` paths relative to the uploaded build context.
Incorrect:
```dockerfile theme={null}
COPY agent-container/base-package.json package.json
```
Correct:
```dockerfile theme={null}
COPY base-package.json package.json
```
## Reduce build timeouts
Internal timeout increases usually resolve build timeout failures automatically. If timeouts continue:
* Install fewer packages in the Dockerfile
* Use a smaller base image where possible
* Combine `RUN` instructions to reduce image layers
## Fix custom entrypoints
If your entrypoint checks ports with `nc`, install netcat:
```dockerfile theme={null}
RUN apt-get update && apt-get install -y netcat-openbsd
```
Use `127.0.0.1` instead of `localhost` in Debian-based images because `localhost` may not resolve correctly.
If you do not need custom initialization, remove the custom script and start the sandbox API directly:
```dockerfile theme={null}
ENTRYPOINT ["/usr/local/bin/sandbox-api"]
```
This entrypoint works when you start other processes programmatically through the SDK or API.
## Install system libraries
Compilation errors such as `unable to find library -lgcc` indicate that the image lacks development packages. Install the required toolchain:
```dockerfile theme={null}
RUN apt-get update && apt-get install -y \
build-essential \
libc6-dev \
libgcc-12-dev \
pkg-config
```
These packages are available during the image build. [Image slimming](/Sandboxes/Templates#image-slimming) removes compilers before the sandbox starts. Disable slimming if the running sandbox needs them.
## Keep build tools available at runtime
A package can install successfully in the Dockerfile and be missing when the sandbox starts. Image slimming runs after the Docker build and removes build-time files, including `make`, `gcc`, `g++`, and `cmake`. Those tools still work in `RUN` steps.
To keep them, disable slimming in `blaxel.toml`:
```toml theme={null}
[build]
slim = false
```
See [image slimming](/Sandboxes/Templates#image-slimming) for the paths this pass removes, and for the effect on image size and boot time.
## Configure the environment
Docker `ENV` variables are available to the entrypoint and its child processes. Extend variables such as `PATH` with `export` in your entrypoint script:
```sh theme={null}
#!/bin/sh
export PATH="/root/.cargo/bin:/root/.local/bin:$PATH"
export LIBRARY_PATH="/usr/lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu:$LIBRARY_PATH"
/usr/local/bin/sandbox-api
```
## Inspect build logs
Use the CLI to view the complete build process and deployment errors:
```bash theme={null}
bl logs sandbox sandbox-name
```
Learn how to run Docker workloads in a sandbox.
Learn how to start and manage processes programmatically.
# WebSocket connection timeouts and the 15-minute limit
Source: https://docs.blaxel.ai/knowledge-base/troubleshooting/websocket-connection-timeouts-and-15-minute-limit
Keep long-running sandbox WebSocket connections reliable by handling the 15-minute idle timeout, reconnection, standby, and ping-pong traffic.
Sandbox WebSocket connections have a 15-minute idle timeout. If no data flows through a connection for 15 minutes, Blaxel closes it and transitions the sandbox to standby.
## Timeout behavior
When a WebSocket connection reaches the 15-minute idle limit, it disconnects automatically. Your client must reconnect when this happens.
## Automatic reconnection
For processes that run longer than 15 minutes, reconnect automatically after the existing connection closes. Reconnection is typically seamless and fast enough for most use cases.
## Sandbox keep-alive
By default, sandboxes enter standby after a few seconds of inactivity to save resources. You can keep a sandbox running after launching a process even when no active client connection exists.
This behavior is useful for a server that must continue running while it is not serving requests. Configure it with the [sandbox keep-alive settings](/Sandboxes/Standby-control).
## Ping-pong traffic
To prevent an idle connection from timing out:
* Send data actively between the client and server
* Exchange regular ping-pong messages to keep the connection active
Configure standby behavior and keep-alive settings.
Learn how to manage interactive sandbox sessions.
# When localhost goes silent in Debian-based sandboxes
Source: https://docs.blaxel.ai/knowledge-base/troubleshooting/when-localhost-goes-silent-in-debian-based-sandboxes
Fix localhost name resolution and service-to-service networking failures after moving sandbox images from Alpine to minimal Debian.
Moving a container image from Alpine to Debian Bookworm can improve compatibility with dependencies such as Playwright or Python 3.13. However, minimal Debian images may introduce a name resolution failure that breaks communication through `localhost`.
## Symptom
A sandbox that worked on Alpine may enter a crash loop after switching to an image such as `node:22-bookworm-slim`. Its logs show an error like:
```text theme={null}
nc: getaddrinfo for host "localhost" port 8080: Name or service not known
```
The error can occur even when `sandbox-api` is running because the system cannot resolve `localhost` to a loopback address.
## Root cause
Slim or minimal Debian distributions may omit the `/etc/hosts` entry or name service configuration in `nsswitch.conf` that resolves `localhost`.
When an application or a utility such as netcat (`nc`) connects to `localhost`, the system performs a name lookup. Without an explicit hosts mapping or suitable resolver configuration, that lookup fails with `getaddrinfo` before the request reaches the network stack.
## Use the loopback IP
Replace `localhost` with `127.0.0.1` in entrypoint scripts, health checks, and tool server configurations. Using the explicit loopback address bypasses hostname resolution.
Configure clients to connect to `127.0.0.1`. Configure servers to listen on `0.0.0.0` when they must also be reachable through external container ports.
## Check a Debian migration
When moving from Alpine to `bookworm-slim`:
* Install `ca-certificates` explicitly to prevent Git clone and HTTPS failures
* Replace `localhost` with `127.0.0.1` in internal scripts
* Verify that `EXPOSE` instructions and port mappings, such as port `8081` for an internal API, match your infrastructure
Learn how networking works inside Blaxel sandboxes.
Configure ports exposed by sandbox services.
# Blaxel SDKs overview
Source: https://docs.blaxel.ai/sdk-reference/introduction
Use the Blaxel SDKs for TypeScript, Python, and Go to programmatically manage sandboxes, agents, MCP servers, jobs, and other resources.
Blaxel features SDKs in three languages: **Python**, **TypeScript**, and **Go**. Check out down below the installation instructions, as well as documentation on **how the SDK** **authenticates** to Blaxel.
## Install
Install the TypeScript SDK.
Install the Python SDK.
Install the Go SDK.
## Prerequisites
To use any Blaxel SDK, you need a [Blaxel account](https://app.blaxel.ai) and the following environment variables:
| Variable | Description |
| - | - |
| `BL_WORKSPACE` | Your Blaxel workspace name |
| `BL_API_KEY` | Your Blaxel API key |
You can create an API key from the [Blaxel console](https://app.blaxel.ai/profile/security). Your workspace name is visible in the URL when you log in to the console (e.g. `app.blaxel.ai/{workspace}`).
Set them as environment variables:
```bash theme={null}
export BL_WORKSPACE=my-workspace
export BL_API_KEY=my-api-key
```
Or add them to a `.env` file at the root of your project.
## Authentication
The Blaxel SDK does not accept credentials as constructor arguments. Credentials must be supplied through one of the following sources, checked in priority order:
1. when running on Blaxel, authentication is handled automatically
2. `BL_WORKSPACE` and `BL_API_KEY` environment variables or `.env` file (see [this page](../Agents/Variables-and-secrets) for other authentication options)
3. configuration file created locally when you log in through [Blaxel CLI](../cli-reference/introduction) (or deploy on Blaxel)
When developing locally, you can also **log in to your workspace with Blaxel CLI:**
```bash theme={null}
bl login
```
This allows you to run Blaxel SDK functions that will automatically connect to your workspace without additional setup. When you deploy on Blaxel, this connection persists automatically.
## Pagination
List endpoints in the Blaxel SDKs return paginated results. Each call fetches one page of items and a cursor that points to the next page. The SDKs expose two ways to consume these pages: auto-paging, which iterates every item across all pages for you, and manual pagination, which gives you control over each page request.
Pagination is cursor-based. Every page carries an opaque `cursor` to the next page and a flag that tells you whether more pages exist. You never build cursors yourself: pass the page through the auto-pager or call the next-page helper, and the SDK forwards the cursor for you.
Each page also reports the total number of items in the workspace, ignoring any filters applied to the request.
### Auto-paging
Auto-paging is the recommended approach. The SDK fetches each page on demand as you iterate, so you can walk an entire result set without tracking cursors.
```typescript TypeScript theme={null}
import { DriveInstance } from "@blaxel/core";
const drives = await DriveInstance.list({ limit: 50 });
for await (const drive of drives) {
console.log(drive.name);
}
```
```python Python (sync) theme={null}
from blaxel.core import SyncDriveInstance
for drive in SyncDriveInstance.list(limit=50).auto_paging_iter():
print(drive.name)
```
```python Python (async) theme={null}
import asyncio
from blaxel.core import DriveInstance
async def main():
drives = await DriveInstance.list(limit=50)
async for drive in drives.auto_paging_iter():
print(drive.name)
asyncio.run(main())
```
```go Go theme={null}
package main
import (
"context"
"fmt"
"github.com/blaxel-ai/sdk-go"
)
func main() {
client := blaxel.NewClient()
iter := client.Drives.ListAutoPaging(context.Background(), blaxel.DriveListParams{})
for iter.Next() {
drive := iter.Current()
fmt.Println(drive.Metadata.Name)
}
if err := iter.Err(); err != nil {
panic(err)
}
}
```
In TypeScript, you can also collect items into an array with `autoPagingToArray({ limit })`, which stops once it reaches the requested number of items.
### Manual pagination
Use manual pagination when you need control over each request, for example to process one page at a time or to stop early based on the page contents. Read the current page from `data`, check whether more pages exist, then fetch the next page.
```typescript TypeScript theme={null}
import { DriveInstance } from "@blaxel/core";
let page = await DriveInstance.list({ limit: 50 });
while (page) {
for (const drive of page.data) {
console.log(drive.name);
}
page = await page.nextPage();
}
```
```python Python (sync) theme={null}
from blaxel.core import SyncDriveInstance
page = SyncDriveInstance.list(limit=50)
while True:
for drive in page.data:
print(drive.name)
if not page.has_more:
break
page = page.next_page()
```
```python Python (async) theme={null}
import asyncio
from blaxel.core import DriveInstance
async def main():
page = await DriveInstance.list(limit=50)
while True:
for drive in page.data:
print(drive.name)
if not page.has_more:
break
page = await page.next_page()
asyncio.run(main())
```
```go Go theme={null}
client := blaxel.NewClient()
page, err := client.Drives.List(context.Background(), blaxel.DriveListParams{Limit: blaxel.Int(50)})
if err != nil {
panic(err)
}
for {
for _, drive := range page.Data {
fmt.Println(drive.Metadata.Name)
}
if !page.Meta.HasMore {
break
}
page, err = page.GetNextPage()
if err != nil {
panic(err)
}
}
```
### Pagination parameters
List endpoints accept the following query parameters:
| Parameter | Description |
| - | - |
| `limit` | Maximum number of items per page. Defaults to `50`. |
| `cursor` | Opaque token pointing to a page. The SDK sets this for you when you use auto-paging or the next-page helper. |
The page object also exposes pagination state so you can inspect it directly: the items (`data`), whether more pages exist (`hasMore` in TypeScript, `has_more` in Python, `Meta.HasMore` in Go), and the next cursor.
### Supported endpoints
Pagination applies to list endpoints across SDK resources. Agent Drives, volumes, and sandboxes support it in all three SDKs. The TypeScript and Python SDKs also paginate a sandbox's schedules and schedule executions (`sandbox.schedules.list` and `sandbox.schedules.executions`), as well as jobs, functions, and agents (`list_jobs`, `list_functions`, and `list_agents`). The Go SDK additionally exposes cursor pagination on its generated list endpoints, including agents, functions, jobs, models, policies, and sandboxes.
You can also paginate results using the [API](/api-reference/introduction#pagination).
## Data collection and privacy
[Read more about the data collected by the Blaxel SDKs](/Security/Data-collection-and-privacy).
## Complete SDK reference
Visit the GitHub pages below for detailed documentation on each SDK's commands and classes.
Open the GitHub repository for Blaxel SDK in TypeScript.
Open the GitHub repository for Blaxel SDK in Python.
Open the GitHub repository for Blaxel SDK in Go.
# Go SDK
Source: https://docs.blaxel.ai/sdk-reference/sdk-go
Install and use the Blaxel Go SDK to programmatically manage sandboxes, agents, MCP servers, jobs, and other workspace resources from Go.
Blaxel features an SDK in **Go**. To install, follow the instructions below.
## Install
```go theme={null}
import (
"github.com/blaxel-ai/sdk-go" // imported as blaxel
)
```
Or to use a specific version:
```shell Go theme={null}
go get -u 'github.com/blaxel-ai/sdk-go@v0.15.0'
```
## Prerequisites
To use this SDK, you need a [Blaxel account](https://app.blaxel.ai) and the following environment variables:
| Variable | Description |
| - | - |
| `BL_WORKSPACE` | Your Blaxel workspace name |
| `BL_API_KEY` | Your Blaxel API key |
You can create an API key from the [Blaxel console](https://app.blaxel.ai/profile/security). Your workspace name is visible in the URL when you log in to the console (e.g. `app.blaxel.ai/{workspace}`).
Set them as environment variables:
```bash theme={null}
export BL_WORKSPACE=my-workspace
export BL_API_KEY=my-api-key
```
Or add them to a `.env` file at the root of your project. For other authentication options, see [Variables and secrets](../Agents/Variables-and-secrets).
Alternatively, you can authenticate via the [Blaxel CLI](../cli-reference/introduction):
```bash theme={null}
bl login
```
When you deploy on Blaxel, authentication is handled automatically.
## Complete SDK reference
Visit the GitHub page below for detailed documentation on the SDK's commands and classes.
Open the GitHub repository for Blaxel SDK in Go.
# Python SDK
Source: https://docs.blaxel.ai/sdk-reference/sdk-python
Install and use the Blaxel Python SDK to programmatically manage sandboxes, agents, MCP servers, jobs, and other workspace resources from Python.
Blaxel features an SDK in **Python**. To install, follow the instructions below.
## Install
```shell Python (pip) theme={null}
pip install blaxel
```
```shell Python (uv) theme={null}
uv pip install blaxel
```
```shell Python (uv add) theme={null}
uv init && uv add blaxel
```
## Prerequisites
To use this SDK, you need a [Blaxel account](https://app.blaxel.ai) and the following environment variables:
| Variable | Description |
| - | - |
| `BL_WORKSPACE` | Your Blaxel workspace name |
| `BL_API_KEY` | Your Blaxel API key |
You can create an API key from the [Blaxel console](https://app.blaxel.ai/profile/security). Your workspace name is visible in the URL when you log in to the console (e.g. `app.blaxel.ai/{workspace}`).
Set them as environment variables:
```bash theme={null}
export BL_WORKSPACE=my-workspace
export BL_API_KEY=my-api-key
```
Or add them to a `.env` file at the root of your project. For other authentication options, see [Variables and secrets](../Agents/Variables-and-secrets).
Alternatively, you can authenticate via the [Blaxel CLI](../cli-reference/introduction):
```bash theme={null}
bl login
```
When you deploy on Blaxel, authentication is handled automatically.
## Guides
Use Blaxel SDK to create and connect to sandboxes and sandbox previews.
Use Blaxel SDK to manage the filesystem, processes and logs of a sandbox.
Use Blaxel SDK to retrieve tools from a deployed MCP server.
Use Blaxel SDK to retrieve an LLM client when building agents.
Use Blaxel SDK to create and host a custom MCP server.
Use Blaxel SDK to chain calls to multiple agents.
## Complete SDK reference
Visit the GitHub page below for detailed documentation on the SDK's commands and classes.
Open the GitHub repository for Blaxel SDK in Python.
# TypeScript SDK
Source: https://docs.blaxel.ai/sdk-reference/sdk-ts
Install and use the Blaxel TypeScript SDK to programmatically manage sandboxes, agents, MCP servers, jobs, and other resources from Node.js.
Blaxel features an SDK in **TypeScript**. To install, follow the instructions below.
## Install
To manage Blaxel resources, use the core SDK `@blaxel/core`:
```shell TypeScript (pnpm) theme={null}
pnpm install @blaxel/core
```
```shell TypeScript (npm) theme={null}
npm install @blaxel/core
```
```shell TypeScript (yarn) theme={null}
yarn add @blaxel/core
```
```shell TypeScript (bun) theme={null}
bun add @blaxel/core
```
For automatic trace and metric exports when running workloads with Blaxel SDK, you'll want to use `@blaxel/telemetry`. Import this SDK at your project's entry point.
```shell TypeScript (pnpm) theme={null}
pnpm install @blaxel/telemetry
```
```shell TypeScript (npm) theme={null}
npm install @blaxel/telemetry
```
```shell TypeScript (yarn) theme={null}
yarn add @blaxel/telemetry
```
```shell TypeScript (bun) theme={null}
bun add @blaxel/telemetry
```
For compatibility with agent’s frameworks (i.e. to import tools and models in the framework’s format), import the corresponding SDK:
```shell TypeScript (pnpm) theme={null}
pnpm install @blaxel/langgraph
pnpm install @blaxel/vercel
pnpm install @blaxel/mastra
pnpm install @blaxel/llamaindex
```
```shell TypeScript (npm) theme={null}
npm install @blaxel/telemetry
npm install @blaxel/vercel
npm install @blaxel/mastra
npm install @blaxel/llamaindex
```
```shell TypeScript (yarn) theme={null}
yarn add @blaxel/telemetry
yarn add @blaxel/vercel
yarn add @blaxel/mastra
yarn add @blaxel/llamaindex
```
```shell TypeScript (bun) theme={null}
bun add @blaxel/telemetry
bun add @blaxel/vercel
bun add @blaxel/mastra
bun add @blaxel/llamaindex
```
## Prerequisites
To use this SDK, you need a [Blaxel account](https://app.blaxel.ai) and the following environment variables:
| Variable | Description |
| - | - |
| `BL_WORKSPACE` | Your Blaxel workspace name |
| `BL_API_KEY` | Your Blaxel API key |
You can create an API key from the [Blaxel console](https://app.blaxel.ai/profile/security). Your workspace name is visible in the URL when you log in to the console (e.g. `app.blaxel.ai/{workspace}`).
Set them as environment variables:
```bash theme={null}
export BL_WORKSPACE=my-workspace
export BL_API_KEY=my-api-key
```
Or add them to a `.env` file at the root of your project. For other authentication options, see [Variables and secrets](../Agents/Variables-and-secrets).
Alternatively, you can authenticate via the [Blaxel CLI](../cli-reference/introduction):
```bash theme={null}
bl login
```
When you deploy on Blaxel, authentication is handled automatically.
## Guides
Use Blaxel SDK to create and connect to sandboxes and sandbox previews.
Use Blaxel SDK to manage the filesystem, processes and logs of a sandbox.
Use Blaxel SDK to retrieve tools from a deployed MCP server.
Use Blaxel SDK to retrieve an LLM client when building agents.
Use Blaxel SDK to create and host a custom MCP server.
Use Blaxel SDK to chain calls to multiple agents.
## Complete SDK reference
Visit the GitHub page below for detailed documentation on the SDK's commands and classes.
Open the GitHub repository for Blaxel SDK in TypeScript.
# Blaxel Skills & MCP
Source: https://docs.blaxel.ai/skills-mcp
Connect Claude, Codex, Cursor, Claude Code, Windsurf, or other AI assistants to manage your Blaxel resources and search the Blaxel docs via MCP.
Blaxel provides LLM-accessible tools that you can connect to your coding assistant (Cursor, Windsurf, Claude Desktop, etc.). There are several options:
1. The Blaxel **plugin** for Claude Code, Codex, and Cursor installs the agent skills and the resource management MCP server together.
2. The Blaxel **agent skills** let your coding agent autonomously create sandboxes, deploy agents, run jobs, and launch MCP servers on Blaxel using simple prompts, with support for both command-line (CLI) and code-driven (SDK) operations.
3. The Blaxel **resource management MCP server** lets your coding agent directly manage your Blaxel resources using natural language.
4. The Blaxel **documentation MCP server** lets your coding agent directly read and ask questions of this documentation in real-time for up-to-date commands and features.
5. An **`llms-full.txt`** text file with the entire documentation compiled and formatted for LLMs.
6. A **native AI assistant** built into this documentation portal.
To set up the skills and both MCP servers at once, [install Blaxel](./Get-started) with `curl -fsSL https://blaxel.ai/install.sh | sh` (on Windows, `irm https://blaxel.ai/install.ps1 | iex`). It adds them to the coding agents on your machine and logs you in. If you already have the Blaxel CLI, run `bl setup`.
## Blaxel plugin
The Blaxel plugin installs the `blaxel-cli` and `blaxel-sdk` [agent skills](#blaxel-skills) and the [resource management MCP server](#mcp-server-for-resource-management) in one step. The MCP server signs you in with OAuth, so no API key goes in your configuration.
Add the Blaxel marketplace first, then install the plugin from it:
```shell theme={null}
claude plugin marketplace add blaxel-ai/agent-skills
claude plugin install blaxel
```
Then run `/mcp` in Claude Code, select `plugin:blaxel:blaxel`, and sign in to Blaxel in your browser.
```shell theme={null}
codex plugin marketplace add blaxel-ai/agent-skills
codex plugin add blaxel@blaxel
codex mcp login blaxel
```
The last command opens Blaxel sign-in in your browser.
```shell theme={null}
cursor-agent plugin marketplace add https://github.com/blaxel-ai/agent-skills
```
Then install **Blaxel** from the marketplace in Cursor and approve the Blaxel sign-in prompt.
In Claude Code, running only the install command fails with `Plugin "blaxel" not found in any configured marketplace`. Add the marketplace first.
## Agent skills
[Agent skills](https://agentskills.io/specification) are instruction sets to extend coding agents with additional knowledge and tools. An agent can load these instructions into its context and use them to complete the tasks assigned to it.
Blaxel offers open source agent skills to help coding agents autonomously spin up and manage sandboxes on Blaxel, and migrate sandbox code from other providers to Blaxel.
### Blaxel skills
The open source [Blaxel skills](https://github.com/blaxel-ai/agent-skills) let agents:
* create perpetual sandboxes on Blaxel to run code and execute commands
* start application servers within sandboxes
* generate URLs to preview applications running within sandboxes
* create Agent Drives (shared filesystems) on Blaxel
* create and deploy AI agents on Blaxel
* create and deploy MCP servers on Blaxel
* deploy and run batch jobs on Blaxel
Two skills are currently available:
* The `blaxel-cli` skill lets coding agents create and manage Blaxel resources from the command line using the `bl` CLI.
* The `blaxel-sdk` skill lets coding agents write code to create and manage Blaxel resources using the Blaxel SDKs.
Use the `blaxel-cli` skill when you're troubleshooting, or bootstrapping a project on Blaxel. Use the `blaxel-sdk` skill when building agents or MCP servers on Blaxel, or when you need to programmatically manage Blaxel resources.
The [Blaxel installer](./Get-started) sets up the Blaxel skills, and `bl upgrade` keeps them up to date. They go to `~/.agents/skills`, which Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and other agents read, and to Claude Code and other coding agents detected on your machine. To install or refresh them at any time, run:
```shell theme={null}
bl skills install
```
To set up the MCP servers too, run `bl setup` instead. Neither needs Node.js. Set `BL_INSTALL_SKILLS=false` to stop the installer and upgrades from installing the skills automatically.
Without the Blaxel CLI, add the skills with the command below. You'll be prompted to choose skills and select your coding agent and installation location.
```shell theme={null}
npx skills add blaxel-ai/agent-skills
```
To install the skills together with the resource management MCP server in Claude Code, Codex, or Cursor, use the [Blaxel plugin](#blaxel-plugin).
The Blaxel skills are also [available as a ZIP file](https://github.com/blaxel-ai/agent-skills/releases) for [use with Claude](https://support.claude.com/en/articles/12512180-use-skills-in-claude).
See [examples of using the Blaxel skills](https://blaxel.ai/blog/give-your-coding-agent-blaxel-superpowers) in our announcement blog post.
### Blaxel migration skill
The open source [Blaxel migration skill](https://github.com/blaxel-ai/agent-migration-skills) gives agents the knowledge they need to migrate sandbox code from other providers to Blaxel. It is currently able to migrate sandbox code from Daytona, E2B and Modal.
Add the Blaxel migration skill to your coding agent with the command below, depending on which sandbox provider you wish to migrate from:
```shell theme={null}
# migrate from Daytona
npx skills add blaxel-ai/agent-migration-skills/daytona
# migrate from E2B
npx skills add blaxel-ai/agent-migration-skills/e2b
# migrate from Modal
npx skills add blaxel-ai/agent-migration-skills/modal
```
You'll be prompted to select your coding agent and installation location.
The Blaxel migration skill is also [available as a ZIP file](https://github.com/blaxel-ai/agent-migration-skills/releases) for [use with Claude](https://support.claude.com/en/articles/12512180-use-skills-in-claude).
## MCP servers
### MCP server for resource management
Blaxel's MCP server lets compatible AI apps (Claude, Codex, Claude Code, Cursor, Devin Desktop, Goose, OpenCode, VS Code, etc.) manage your Blaxel resources using natural language. It is available as a **remote hosted HTTP server**.
The [Blaxel installer](./Get-started) and `bl setup` add it, with the [documentation MCP server](#mcp-server-for-documentation), to Claude Code, Codex, Cursor, Gemini CLI, OpenCode, Windsurf and Devin Desktop on your machine, and tell you how to sign each one in. To add it to another client, or by hand, follow the steps below.
Talk with your preferred agent to:
* Create new services
* Follow what is happening on your infrastructure
* Manage your users
#### How it works
The MCP server is hosted at `https://api.blaxel.ai/v0/mcp`.
Clients connect over HTTPS and stream MCP messages. The server supports OAuth 2.1 sign-in and API key authentication.
##### OAuth
Use OAuth when your client supports it. Your client opens Blaxel sign-in in a browser and stores the resulting token.
The server advertises its authorization server at `https://signin.blaxel.ai`. It supports PKCE with the `S256` challenge method, dynamic client registration, Client ID Metadata Documents, and public clients. Requested scopes are `openid`, `email`, `profile`, and `offline_access`.
##### Authentication headers
For clients without OAuth support, provide your [API key](/Security/Access-tokens) in the `Authorization` header. If your user has access to multiple workspaces, include the workspace header as well.
* Required: `Authorization: Bearer `
* Optional (multi-workspace): `X-Blaxel-Workspace: `
Store your API key in an environment variable. Do not commit it to a repository.
#### Installation
You need a Claude account, a Blaxel account, and access to a Blaxel workspace. Custom connectors are available on individual Claude accounts as well as Team and Enterprise plans. Organization policies can restrict which connectors you can add.
In Claude, open Customize → Connectors and select Add custom connector.
Enter `Blaxel` as the name and `https://api.blaxel.ai/v0/mcp` as the remote MCP server URL.
Leave OAuth Client ID and OAuth Client Secret blank. Keep Individual sign-in enabled if Claude shows that option.
Add the connector and select Connect if prompted. Complete Blaxel sign-in and review the authorization screen before continuing back to Claude.
Enable Blaxel in a conversation and ask: "Use Blaxel to list the agents in workspace YOUR\_WORKSPACE\_NAME."
Replace `YOUR_WORKSPACE_NAME` with your workspace name. Confirm that the response identifies resources from that workspace.
Available actions depend on your Blaxel workspace role. Service-account management requires an admin or owner. Review the workspace and resource names before approving write actions. Starting a batch job returns execution details; it does not wait for the execution to finish.
If your account has several workspaces, name the intended workspace in your request. To reconnect after an authorization error, open the connector in Claude's Connectors settings and complete sign-in again.
##### Codex
Add the hosted server. Codex detects OAuth support and starts the sign-in flow:
```bash theme={null}
codex mcp add blaxel --url https://api.blaxel.ai/v0/mcp
```
To sign in again later, run:
```bash theme={null}
codex mcp login blaxel
```
To use an API key instead, set it in your shell and reference it from `~/.codex/config.toml`:
```bash theme={null}
export BLAXEL_MCP_API_KEY=""
```
```toml theme={null}
[mcp_servers.blaxel]
url = "https://api.blaxel.ai/v0/mcp"
bearer_token_env_var = "BLAXEL_MCP_API_KEY"
```
##### Claude Code
[Add the remote HTTP server to Claude Code](https://docs.anthropic.com/en/docs/claude-code/mcp#option-3%3A-add-a-remote-http-server) with user scope, so it is available in all your projects:
```bash theme={null}
claude mcp add --scope user --transport http blaxel https://api.blaxel.ai/v0/mcp
```
Sign in to Blaxel in your browser:
```bash theme={null}
claude mcp login blaxel
```
You can also run `/mcp` in Claude Code, select `blaxel`, and follow the authentication prompts.
To use an API key instead, set the headers when you add the server:
```bash theme={null}
claude mcp add --scope user --transport http blaxel https://api.blaxel.ai/v0/mcp \
--header "Authorization: Bearer " \
--header "X-Blaxel-Workspace: "
```
##### Cursor
Add the server to `~/.cursor/mcp.json`:
```json theme={null}
{
"mcpServers": {
"blaxel": {
"url": "https://api.blaxel.ai/v0/mcp"
}
}
}
```
Open Cursor settings, select MCP, then connect `blaxel`. Cursor opens Blaxel sign-in.
Cursor Agent users can start sign-in from a terminal:
```bash theme={null}
cursor-agent mcp login blaxel
```
When you run `cursor-agent` in your home folder, `~/.cursor/mcp.json` is also the project config, so `cursor-agent mcp list` shows each server in it, such as `blaxel`, as `not loaded (needs approval)`. Approve each once, for example `cursor-agent mcp enable blaxel` (and `cursor-agent mcp enable blaxel-docs` if you added the documentation server). A server needs approval again if its config changes.
To use an API key instead, add the headers to the server entry:
```json theme={null}
{
"mcpServers": {
"blaxel": {
"url": "https://api.blaxel.ai/v0/mcp",
"headers": {
"Authorization": "Bearer ",
"X-Blaxel-Workspace": ""
}
}
}
}
```
##### Devin Desktop (formerly Windsurf)
Add the server to `~/.config/devin/mcp_config.json` (Windows: `%APPDATA%\devin\mcp_config.json`). Windsurf releases from before the rename use `~/.codeium/windsurf/mcp_config.json`.
```json theme={null}
{
"mcpServers": {
"blaxel": {
"serverUrl": "https://api.blaxel.ai/v0/mcp"
}
}
}
```
Refresh the MCP servers in Cascade and complete the Blaxel sign-in in your browser. To use an API key instead, add a `headers` object with the [authentication headers](#how-it-works).
##### Goose
Add the server to `~/.config/goose/config.yaml` (Windows: `%APPDATA%\Block\goose\config\config.yaml`):
```yaml theme={null}
extensions:
blaxel:
enabled: true
type: streamable_http
name: blaxel
description: Blaxel resource management
uri: https://api.blaxel.ai/v0/mcp
timeout: 300
```
Goose opens Blaxel sign-in in your browser the first time it connects. To use an API key instead, add a `headers` map with the [authentication headers](#how-it-works).
##### OpenCode
Add the hosted server to your global OpenCode config, then sign in:
```bash theme={null}
opencode mcp add blaxel --url https://api.blaxel.ai/v0/mcp
opencode mcp auth blaxel
```
##### VS Code
Add the hosted server to your VS Code user profile:
```bash theme={null}
code --add-mcp '{"name":"blaxel","type":"http","url":"https://api.blaxel.ai/v0/mcp"}'
```
VS Code prompts you to sign in to Blaxel when it starts the server.
#### Selecting a workspace
Every tool accepts an optional `workspace` argument. Ask your client to use that argument when you want a tool to act on a specific workspace.
If your account has multiple workspaces, you can also set the active workspace by including the `X-Blaxel-Workspace` header. If neither is set, your AI app may prompt you to specify one when a tool is invoked.
#### Example prompts
Ask your AI app to perform actions using Blaxel MCP tools, for example:
* List my agents
* Create a new MCP server named search with blaxel-search integration
* Invite [test@mydomain.com](mailto:test@mydomain.com) to my workspace
#### Supported actions
The Blaxel MCP server exposes tools for common platform operations.
You can list, create and remove services (Sandboxes, Agents, MCP Servers, Jobs, Models, …), as well as manage users and service accounts.
See the [open-source definitions](https://github.com/blaxel-ai/blaxel-mcp-server) for the latest list and schemas.
#### Running locally
Prefer using the hosted server at `https://api.blaxel.ai/v0/mcp`. If you must run locally or customize behavior, clone the repository and follow its instructions.
#### Limitations
* The server requires an OAuth sign-in or a valid API key.
* When using multiple workspaces, set the `workspace` argument or the `X-Blaxel-Workspace` header to disambiguate.
Check out Blaxel MCP server's [source here](https://github.com/blaxel-ai/blaxel-mcp-server).
### MCP server for documentation
You can also give your coding assistant real-time access to this documentation.
Connect your coding assistant directly to the Blaxel documentation via MCP at `https://docs.blaxel.ai/mcp`.
#### Claude Code
[Add the remote HTTP server to your Claude Code](https://docs.anthropic.com/en/docs/claude-code/mcp#option-3%3A-add-a-remote-http-server) by running the following command:
```bash theme={null}
claude mcp add --scope user --transport http blaxel-docs https://docs.blaxel.ai/mcp
```
#### Cursor
1. Open **Cursor Settings**
2. Go to **MCP & Integrations**
3. Click **"+ Add a custom MCP server"**
4. Add this configuration:
```json theme={null}
{
"mcpServers": {
... // Your other MCP servers
"blaxel-docs": {
"url": "https://docs.blaxel.ai/mcp"
}
}
}
```
### MCP server for sandboxes
Every Blaxel sandbox also exposes its own built-in [MCP server](/Sandboxes/MCP), hosted via streamable HTTP at `https:///mcp`, so agents can operate that specific sandbox directly.
### MCP server hosting
Blaxel also lets you deploy and host your [own MCP servers](/Functions/Overview) as serverless, autoscaling endpoints.
### MCP endpoints at a glance
| Surface | Endpoint pattern | Purpose | Persistence |
| - | - | - | - |
| Resource management | `https://api.blaxel.ai/v0/mcp` | Manage your Blaxel workspace (create/list/delete resources) from a coding assistant | Fixed platform endpoint |
| Documentation | `https://docs.blaxel.ai/mcp` | Give a coding assistant real-time access to this documentation | Fixed platform endpoint |
| Per deployed MCP server | `https://run.blaxel.ai/{workspace}/functions/{server-name}/mcp` | Call tools exposed by an MCP server deployed by you | Fixed so long as the function's name is unchanged |
| Per deployed sandbox | `https:///mcp` | Let an agent operate a sandbox deployed by you | Tied to the sandbox's `name`; fixed across standby/resume. |
## llms-full.txt
A compiled text document of the entire documentation, formatted for LLMs. Copy and paste it directly into your coding assistant's prompt:
[https://docs.blaxel.ai/llms-full.txt](https://docs.blaxel.ai/llms-full.txt)
## Built-in documentation agent
This documentation portal has a built-in AI assistant. Click "✨**Ask AI**" at the top of any page to use it.
# Troubleshoot Blaxel deployments, runtime, and CLI errors
Source: https://docs.blaxel.ai/troubleshooting
Resolve common issues with Blaxel deployments including build failures, runtime errors, connectivity problems, and CLI authentication.
This page describes common issues and how to troubleshoot them. If your issue is not covered below, browse the [Blaxel Knowledge Base](/knowledge-base/overview).
For current service status, refer to [https://status.blaxel.ai/](https://status.blaxel.ai/)
## Preview URLs return a `502 Bad Gateway` server error
This error occurs when the application server is either not running inside the sandbox, or is running but binding to `localhost`. In the latter case, it is only reachable inside the sandbox and the external preview URL will not be able to access it.
To resolve this error, first confirm that the application server is running. If it is, then ensure that it is configured to bind to the address provided by the `HOST` environment variable, which Blaxel injects into the sandbox, rather than a static value. Once this is done, restart the application server. If you need a static fallback (for example when running the same code outside a sandbox), use `::` so it listens on all available network interfaces over both IPv4 and IPv6 (use `0.0.0.0` only if you specifically need to restrict the server to IPv4).
For example, in code:
```tsx TypeScript theme={null}
const host = process.env.HOST || '::';
app.listen(3000, host, () => {
console.log(`Server is running`);
});
```
```python Python theme={null}
host = os.environ.get("HOST", "::")
app.run(host=host, port=3000)
```
or at the command line:
```tsx TypeScript theme={null}
npm run dev -- --host $HOST
# or
npm serve -- --host $HOST
```
```python Python theme={null}
python main.py --host $HOST
```
## Preview URL requests fail on the browser's CORS preflight (OPTIONS) request
This error occurs when calling a preview URL from browser-based JavaScript on a different origin, and the browser's CORS preflight (`OPTIONS`) request doesn't get the expected headers back. It typically shows up as a CORS error in the browser console, and the actual request never reaches your sandbox.
To resolve this error, configure `responseHeaders` on the preview with the appropriate CORS headers. Make sure `Access-Control-Allow-Origin` matches the calling origin exactly, and that `Access-Control-Allow-Headers` includes every custom header your client sends, including any authentication-related headers. See [Handle CORS on Blaxel](/Sandboxes/CORS) for details.
## Sandbox commands return a `502 Bad Gateway` server error
This error occurs for three possible reasons:
1. The sandbox API is called too soon after sandbox creation. The solution is to retry the request.
2. The sandbox API was unexpectedly terminated - for example, if the sandbox runs out of memory. The solution is to delete and recreate the sandbox, which also restarts the sandbox API.
3. The sandbox never started so the deployment status is set to `ERROR`. The solution is to recreate the sandbox.
The sandbox logs will typically contain additional information to identify the root cause of this error. Sandbox logs can be viewed in the [Blaxel Console](https://app.blaxel.ai) or with the Blaxel CLI using the command `bl logs sandbox SANDBOX-NAME`.
Deleting and recreating will cause the current state of the sandbox to be lost.
## Sandbox creation returns a `504 Gateway Timeout`
This error occurs when a sandbox creation request takes longer than CloudFront's edge timeout (approximately 60 seconds) to complete. CloudFront closes the connection to the origin and returns an HTML `504 Gateway Time-out` page rather than a Blaxel JSON error. This does **not** mean sandbox creation failed; the build and deployment continue in the background.
To resolve this error, poll the sandbox's `status` field until it reaches `DEPLOYED` or `FAILED`, using the polling pattern shown in the [custom image deployment guide](/Sandboxes/Templates#4-monitor-deployment-status).
The TypeScript SDK's `SandboxInstance.create()` handles this automatically by falling back to polling on a 504, so this only needs manual handling when calling the REST API directly or using other language SDKs.
## Sandbox deployment fails with `STARTUP TCP probe failed` or `DEADLINE_EXCEEDED` error
This error occurs when the server running inside the sandbox is not binding to the correct host and port.
To resolve this error, configure your server to bind to the host and port designated by the `HOST` and `PORT` environment variables. Blaxel automatically injects these variables during deployment.
For example:
```tsx TypeScript theme={null}
const port = parseInt(env.PORT || "80");
const host = process.env.HOST || "::";
app.listen(port, host, () => {
console.log(`Server is running`);
});
```
```python Python theme={null}
port = os.environ.get("PORT", "80")
host = os.environ.get("HOST", "::")
app.run(host=host, port=port)
```
## Sandbox deployment fails with a `QUOTA_EXCEEDED` error or a 400 status code
This error indicates that the maximum memory limit for the account has been reached in the current tier.
Blaxel has a tiering system that unlocks higher limits and features on the platform as your tier progresses. To resolve this error, upgrade your account to a higher tier, which unlocks higher RAM limits for sandboxes.
To see more details on which quota was exceeded or to upgrade to a higher tier, visit the [**Quotas** page of the Blaxel Console](https://app.blaxel.ai/account/quotas).
## Models return a 429 error
This error occurs when a model reaches its maximum token limit.
To resolve this error, adjust the model's token usage policy. Token usage policies control the maximum number of tokens your model APIs can handle within a specific time period. You can configure the maximum number of input tokens, output tokens and/or total tokens using these policies.
## Sandbox commands return an `ENOSPC: no space left on device` error
This error occurs when your sandbox runs out of storage space. This usually happens when your file storage requirements exceed the available memory since, for performance reasons, Blaxel sandboxes reserve, when possible, [approximately 50% of their available memory](/Sandboxes/Overview#memory-and-filesystem) for the filesystem.
To resolve this error, you have two options:
* Increase the available memory allocation for the sandbox, which also increases the available filesystem storage.
Increasing memory also increases CPU allocation.
* 8GB memory = 4 CPU cores
* 16GB memory = 6 CPU cores
* Add storage using [volumes](/Sandboxes/Volumes) when data should live at a specific mount path or persist independently from the sandbox lifecycle. Adding storage requires deleting and recreating the sandbox first.
Volumes are slower than the in-memory filesystem. Refer to our [blog post on choosing the right storage for your Blaxel agents](https://blaxel.ai/blog/choose-the-right-storage-for-your-blaxel-agents) for our benchmarks.
## API requests fail with a 403 error even though the credentials are valid
This error occurs when the credentials themselves (API key or OAuth token) are valid, but the user or service account they belong to doesn't have sufficient privileges in the specified workspace.
Two related errors can also occur:
* A `400` error if the request doesn't specify a workspace at all where one is required.
* A `400` or `404` error if the credential has access to zero or multiple workspaces and none was specified, since Blaxel can't determine which one to use.
This is distinct from a `401 Unauthorized` error, which means the [credentials themselves are missing or invalid](/troubleshooting/error-codes).
To resolve this error, first confirm the workspace name in your request matches a workspace you (or the service account) have access to. Also check your [role in that workspace](/Security/Workspace-access-control) and ask a workspace administrator to grant the required role if it's missing or insufficient.
Personal API keys and OAuth logins grant access to every workspace you belong to, while [service account](/Security/Service-accounts) API keys and client-credentials tokens are scoped to a single workspace. Ensure that you're using credentials that are actually valid for the workspace you're targeting.
## Sandbox commands return a `404 WORKSPACE_NOT_FOUND` error
This error occurs when a [workspace is not provisioned correctly](/troubleshooting/error-codes).
To resolve this error, please contact us for support via [Discord](https://discord.gg/CsWKUZUHFQ) or our [online contact form](https://blaxel.ai/contact).
## Volume creation fails with a 403 error
This error could occur when:
* volumes are not enabled for the workspace;
* volume size exceeds the maximum allowed size;
* total volume size exceeds maximum allowed total size.
To resolve this error, you can:
* Enable volumes (if not enabled).
* Reduce the size of your volumes.
* Contact us to increase your volume size limits.
## Connections to the sandbox terminal fail with `websocket: bad handshake`
This error could occur when:
* you are using an outdated CLI version
* you are using a custom sandbox image without the sandbox API
* you are using a deprecated image
To resolve this error, you can:
* upgrade to the latest CLI version
* if using a pre-built image, ensure that you are using a [current version of the image](/Sandboxes/Templates#pre-built-templates). If using an older image, recreate the sandbox with the latest version.
* if using a custom Docker image, ensure that you include the sandbox API. If it's missing, add it to your image build process and recreate the sandbox with the new image.
## Hot reloads fail on Webpack previews
This error occurs when a Webpack server is configured to use the same port as local development, but the sandbox preview URL is mapped to a different port. As a result, when hot reloading previews, your client is trying to connect to the local development port instead of the sandbox's preview URL port.
To resolve this error, add a conditional in the `webSocketURL` of your `webpack/config.dev.js` to handle the different ports:
```javascript highlight={9-13} theme={null}
///... rest of webpack/config.dev.js
module.exports = {
devServer: {
host: "::",
port: 3000,
allowedHosts: [".bl.run", ".beamlit.net", "localhost", "127.0.0.1"],
client: {
webSocketURL: {
port: process.env.BL_CLOUD === "true" ? 443 : 3000,
},
},
headers: {
...
},
},
/// ... rest of webpack/config.dev.js
```
## A deleted agent or MCP server still appears in the Blaxel Console listing
This error occurs when a deletion operation didn't fully complete, leaving the resource in a stuck "deleted" state.
To resolve this, run the `bl delete` command again on the same resource:
```bash theme={null}
bl delete agent
# or
bl delete function
```
# Error codes reference
Source: https://docs.blaxel.ai/troubleshooting/error-codes
Reference for structured error codes returned by Blaxel API responses, including the JSON body format and headers designed for LLMs and coding agents.
Blaxel returns structured error responses in case of request failure. These error responses include additional information designed for coding agents and LLMs.
Every error has a consistent JSON body and response header, as shown below:
```json theme={null}
{
"error": {
"action": "Stop. URL does not match a Blaxel route. Use `https://{sbx|agt|mcp|mdl|job}-{name}-{workspace}.{region}.bl.run[/{path}]` for workloads or `https://{preview_id}.preview.bl.run/{path}` for previews. Get the canonical URL from the SDK (e.g. `sandbox.metadata.url`), not by hand.",
"code": "ROUTE_NOT_FOUND",
"do_not": "Do not retry, do not guess URLs, do not use `run.blaxel.ai` (not served).",
"docs_url": "https://docs.blaxel.ai/Sandboxes/Ports.md",
"message": "Preview not found: xxx",
"origin": "platform",
"retryable": false,
"status": 404,
"timestamp": "2026-04-23T14:49:43+00:00"
}
}
```
| Field | Description |
| - | - |
| `error.action` | Agent-readable actions to be taken |
| `error.code` | Machine-readable error code |
| `error.do_not` | Agent-readable actions not to be taken |
| `error.docs_url` | Documentation related to this error |
| `error.message` | Human-readable description |
| `error.origin` | `"platform"` when emitted by Blaxel; otherwise absent |
| `error.retryable` | Whether the request can return a different response if it is retried |
| `error.status` | HTTP status code (same as HTTP response header) |
| `error.timestamp` | Timestamp in ISO-8601 format |
## Error codes
| Code | HTTP | Retryable | When emitted |
| - | - | - | - |
| `ROUTE_NOT_FOUND` | 404 | No | Request path couldn't be parsed into a known workload, preview, or sandbox subdomain shape |
| `WORKLOAD_NOT_FOUND` | 404 | No | Workload record doesn't exist |
| `WORKSPACE_NOT_FOUND` | 404 | No | Workspace couldn't be resolved for a deployment-subdomain request |
| `WORKLOAD_UNAVAILABLE` | 404 | **Yes** | Workload exists but no healthy replica is currently serving it |
| `AUTHENTICATION_REQUIRED` | 401 | No | Credentials missing |
| `AUTHENTICATION_FAILED` | 401 | No | Credentials present but invalid or rejected |
| `FORBIDDEN` | 403 | No | Authenticated but not authorized |
| `BAD_REQUEST` | 400 | No | Malformed request |
| `USAGE_LIMIT_EXCEEDED` | 402 | No | Plan quota reached |
| `POLICY_VIOLATION` | varies | No | Policy rule rejection |
| `REQUEST_ENTITY_TOO_LARGE` | 413 | No | Request body exceeds [upload limit](/Sandboxes/Filesystem#write-binary-file) |