# 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. image.webp 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). image.webp 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. image.webp # 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) ![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXcFD1le5fn-eMiyTjOee-bHWxp1UjI1AGyCszco9b-mdjaIoN1mnDtOmKbEAW8LsocvYGiroJJwCi_SHFgzKLwYoHoG5UweSDlS3Smm-r0f0RMvr85KCx4Rp_6MdhwweRdRcBpeaQ?key=Pe48uQJwhyRkAD1JsBWU4Q) * 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 ![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXcikg83r7fMeZkFl_4JM0evSGUH7oj_EsJtMMjYHwrtiHZrq_1smDrui7kpxbPy-Vqj9-Y6P0pazODg1vPxG5S_Rh5eeqADVjatdfXqukuKc9kx6rGYNbnmnAsnIZfQCOdCxlBmJw?key=Pe48uQJwhyRkAD1JsBWU4Q) * Click on “Add or remove scopes” * Add `gmail.send` ![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXeiuINtnWLiOcK94UIMLCoqc7N7L-gQRtvXdKcTmab1yvTpLTwWZ_jvw8fy1ygeONhm8lRsKnTjP_-M6xuzOh-wE0FAm7_qgtgMAAQnP00Y0IVXOvCGt2aT3Inf-tUxvwUclvbi?key=Pe48uQJwhyRkAD1JsBWU4Q) 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. image.webp ## 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. List IPs * Select the quantity and egress gateway region. Order IPs 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. Assign IP ### 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`): inbox.webp From the same dashboard, obtain an API key: api.webp ## 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). deployment.webp 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. image.webp ## 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. image.webp 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** azure1.webp On Blaxel, in Workspace Settings > Azure AI Inference integration, create a new connection and paste the endpoint and the Access token there. Screenshot 2025-01-31 at 2.29.05 PM.webp ### 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. azure2.webp azure3.webp 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** azure4.webp On Blaxel, in Workspace Settings > Azure Marketplace integration, create a new connection and paste this token into the “Access token” section. image.webp 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. image.webp 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. Screenshot 2024-12-06 at 2.13.40 PM.webp ## 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. Screenshot 2024-12-06 at 2.14.58 PM.webp 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. image.webp ## 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. image.webp 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. image.webp ### 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. image.webp ## 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. hf-integration.webp ## 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. image.webp 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). image.webp * **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. image.webp ## 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. Screenshot 2024-12-06 at 10.41.25 AM.webp ## 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. Screenshot 2024-12-06 at 10.46.23 AM.webp 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. Screenshot 2024-12-06 at 2.18.45 PM.webp ## 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. image.webp 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. image ## 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. image.webp ## 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. image.webp ## 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. Screenshot 2024-12-05 at 6.59.49 PM.webp 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. workbench.webp # 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 image.webp image.webp ### 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 image.webp ### 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`. traces.webp #### 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: process logs ### 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). Lifecycle of Blaxel Sandboxes Lifecycle of Blaxel Sandboxes [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: image.webp * From your local host, by running: ```bash theme={null} bl connect sandbox your-sandbox-name ``` image.webp 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: Screenshot 2025-05-06 at 10.50.49 PM.webp ### 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. Configure fallback preview 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: process logs ### 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. Disable process logging 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 && (