APIZDocs

APIZ guide

Modal Integration

Use the native Modal SDK to create and operate your sandbox. APIZ supplies credential issuance and an optional installation command; no sandbox object is passed to APIZ. You retain images, files, commands, timeouts and deletion.

Prerequisites And Installation

First follow the Python installation or TypeScript installation. Install the optional provider package in that same host environment:

# In the virtual environment created by the Python guide
python -m pip install modal==1.5.5
# In your Node host project
npm install [email protected]

These are tested native SDK versions, not a promise of all future versions. Configure the four host APIZ values APIZ_USER_API_KEY, APIZ_SERVER_URL, APIZ_TEAM_ID, APIZ_CLIENT_ID from the language tutorial. Additionally set MODAL_TOKEN_ID, MODAL_TOKEN_SECRET, MODAL_APP_NAME, MODAL_IMAGE on the host. Provider credentials never go to the guest. Your Client must already have the intended Bindings and Policies configured.

The sandbox needs sh, tar, a supported downloader, CA certificates, a writable temporary directory and outbound access to APIZ and its CLI downloads. Native creation options must be chosen by the caller; APIZ does not create a provider template or bypass firewalls.

Python: Create, Grant, Run, Revoke, Delete

Save this as host.py in your host project. With the variables exported, run python host.py in that project with your virtual environment active. This is a live readiness exercise that creates a cloud sandbox; it does not make an upstream service request. Output from CLI inspect is captured.

import os

from apiz.management import Management
from apiz.access import issue_access
from apiz.cli import install_command
import modal

with Management(
    api_key=os.environ["APIZ_USER_API_KEY"],
    server_url=os.environ["APIZ_SERVER_URL"],
    team_id=os.environ["APIZ_TEAM_ID"],
) as api:
    provider = modal.Client.from_credentials(
        os.environ["MODAL_TOKEN_ID"], os.environ["MODAL_TOKEN_SECRET"]
    )
    app = modal.App.lookup(os.environ["MODAL_APP_NAME"], client=provider)
    sandbox = modal.Sandbox.create(
        app=app,
        client=provider,
        image=modal.Image.from_registry(os.environ["MODAL_IMAGE"]),
        timeout=600,
    )
    try:
        process = sandbox.exec(
            "sh", "-c", install_command(server_url=api.server_url), timeout=120
        )
        if process.wait() != 0:
            raise RuntimeError("CLI installation failed")
        with issue_access(
            api, client_id=os.environ["APIZ_CLIENT_ID"], ttl_seconds=600
        ) as access:
            command = "/tmp/apiz/bin/apiz --output json client handoff inspect"
            process = sandbox.exec(
                "sh", "-c", command, env=dict(access.export_env()), timeout=30
            )
            output = process.stdout.read()  # Captured, not printed.
            if process.wait() != 0:
                raise RuntimeError("Guest command failed")
        print("PASS: CLI access ready and credential group revoked")
    finally:
        sandbox.terminate(wait=True)

TypeScript SDK: Native Node Host

Save as host.mjs in the Node project where you installed both packages. With variables exported, run node host.mjs, or put them in a local .env and run node --env-file=.env host.mjs. This uses the same live readiness flow.

import { Management } from "@apiz/sdk/management";
import { issueAccess } from "@apiz/sdk/access";
import { installCommand } from "@apiz/sdk/cli";
import { ModalClient } from "modal";

/** @param {string} name */
function required(name) {
  const value = process.env[name];
  if (!value) throw new Error(`Set ${name}`);
  return value;
}

const api = new Management({
  apiKey: required("APIZ_USER_API_KEY"),
  serverUrl: required("APIZ_SERVER_URL"),
  teamId: required("APIZ_TEAM_ID"),
});
try {
  const provider = new ModalClient({
    tokenId: required("MODAL_TOKEN_ID"),
    tokenSecret: required("MODAL_TOKEN_SECRET"),
  });
  const app = await provider.apps.fromName(required("MODAL_APP_NAME"));
  const image = provider.images.fromRegistry(required("MODAL_IMAGE"));
  const sandbox = await provider.sandboxes.create(app, image, {
    timeoutMs: 600_000,
  });
  try {
    const installed = await sandbox.exec(
      ["sh", "-c", installCommand({ serverUrl: api.serverUrl })],
      { timeoutMs: 120_000 },
    );
    if ((await installed.wait()) !== 0)
      throw new Error("CLI installation failed");
    const access = await issueAccess(api, {
      clientId: required("APIZ_CLIENT_ID"),
      ttlSeconds: 600,
    });
    await access.withAccess(async () => {
      const command = "/tmp/apiz/bin/apiz --output json client handoff inspect";
      const process = await sandbox.exec(["sh", "-c", command], {
        env: access.exportEnv(),
        timeoutMs: 30_000,
      });
      const output = await process.stdout.readText(); // Captured, not printed.
      if ((await process.wait()) !== 0) throw new Error("Guest command failed");
      void output;
    });
    console.log("PASS: CLI access ready and credential group revoked");
  } finally {
    await sandbox.terminate({ wait: true });
  }
} finally {
  await api.close();
}

Run Your Agent Or The Official-Tool Guest

After sandbox creation and before granting access, use the provider's native file API to upload your agent. For the shared S3 guest, read the guest_s3.py file from the guest tutorial into guest_source / guestSource, then upload it:

sandbox.filesystem.write_bytes(guest_source.encode(), "/tmp/guest_s3.py")
await sandbox.filesystem.writeBytes(
  Buffer.from(guestSource),
  "/tmp/guest_s3.py",
);

Install Python/boto3 with native commands if the chosen image lacks them. Add APIZ_EXAMPLE_BINDING and APIZ_EXAMPLE_OBJECT_KEY through the grant's env option, together with APIZ_CLI_PATH=/tmp/apiz/bin/apiz, then replace the inspect command with python /tmp/guest_s3.py. The full setup/official SDK flow and expected Access Logs are explained in the Python tutorial. For your own agent, substitute its installed entrypoint and configure its official tools with APIZ CLI setup.

Async Python And Explicit Lifetime

Modal uses .aio on its native functions: await sandbox.exec.aio(...), process.stdout.read.aio() and process.wait.aio(). Use issue_access_async with AsyncManagement for credentials, then pass exported env to native async commands.

with is optional: save the access object and call revoke() in finally; async callers use await access.revoke(). Inspect the cleanup report, especially unknown. TypeScript offers withAccess or explicit await access.revoke(). See Python lifecycle cases and TS lifecycle cases.

Provider-Specific Behavior

App, image and client configuration

The snippets look up an existing Modal app. Set MODAL_APP_NAME to its name and MODAL_IMAGE to a registry image you control/choose with sh, curl, CA certificates and a writable temporary directory. For the S3 tutorial also provide Python and pip. An APIZ-maintained image/template is not required.

If your host configuration calls the keys MODAL_API_TOKEN_ID and MODAL_API_TOKEN_SECRET, map them explicitly to the two values used here; the APIZ integration does not discover or rename provider keys. Keep the configured Modal client alive through preparation and revocation.

Read process output and then wait for the exit code as shown. A returned process handle alone does not mean command success. If termination waiting times out, retain the native sandbox ID and query Modal before assuming resource deletion.

Cleanup And Troubleshooting

SymptomAction
Native creation failsCheck provider key, account, region/runtime and quota before changing APIZ configuration
Installation or inspect failsCheck CLI prerequisites and DNS/TLS/egress; native SDK errors remain native errors
Inspect works but agent request is deniedVerify Binding, Policy and official-tool setup; find the request in Access Logs
Revocation is unknownReconcile the saved APIZ operation/group ID; repeated access-object revoke calls use a cached report
Sandbox cleanup is unknownQuery the provider using its native sandbox ID; do not assume the resource stopped

Revoke before closing management/provider scopes. Sandbox destruction and APIZ revocation are independent operations; deleting a sandbox does not replace credential revocation. Expiry is a fallback, not proof that cleanup succeeded. Do not capture active credential environments in reusable snapshots.

Support And Validation Scope

Earlier hosted Python/TS CLI-tool acceptance is recorded for Modal. This explicit installation/issuance revision requires a fresh hosted run before claiming hosted acceptance.

Native types and local toolchain tests do not establish connectivity for your account. Before relying on the integration, run a representative allowed and denied service request, inspect Access Logs, and confirm revocation and native sandbox cleanup.

Native reference: Modal documentation.