Run a code snippet in a sandboxed interpreter
Executes a program in a throwaway sandbox and answers with what it printed and what it left behind.
POST /v1/exec
| Address | https://api.hanzo.ai/v1/exec |
| Method | POST |
| Operation | post_exec |
| Auth | Authorization: Bearer $HANZO_API_KEY |
Executes a program in a throwaway sandbox and answers with what it printed and what it left behind.
lang names one of the thirteen the sandbox image carries — py, js, ts, bash, r,
php, go, rs, c, cpp, java, d, f90 — and code is the whole program, not a
fragment: a compiled language is compiled and then run, an interpreted one is
interpreted, and args becomes the program's own argv either way. Nothing is
installed for you; the image is the environment.
A PROGRAM THAT FAILS IS A SUCCESSFUL CALL. A non-zero exit answers 200 with the
diagnostics on stderr, because "the code threw" and "the interpreter is down"
are different facts a caller renders differently. Only the second is an error
status.
Runs are stateful through session_id. Omit it and the run gets a fresh sandbox
whose id comes back on the answer; pass that id again and the next run sees the
same filesystem, so a program can write a file one call and read it the next.
files names bytes already uploaded to a session (POST /v1/exec/upload), copied in
before the program starts. files on the ANSWER is what the program created or
changed, by comparison against a marker taken at start — so it is the run's real
output, not a listing of the directory — and each is fetched from
GET /v1/exec/download/{session}/{name}.
The tenant is the caller's, never the body's, at every door. A typed op is also an MCP tool and an op-plane op; MCP's tools/call invokes it directly, with no route and therefore no middleware, so nothing there could have checked a credential. tenantOf refuses a context carrying neither a validated principal nor exec's own admission marker, so those doors fail closed without a second gate to keep in step.
Request
11 fields, body application/json (required).
| Field | In | Type | Required | Description |
|---|---|---|---|---|
args | body | string[] | — | Args become the PROGRAM's argv, never the compiler's. |
code | body | string | yes | Code is the WHOLE program, not a fragment: it is written to a single file and that file is what runs, so a compiled language needs its entry point and an… |
files | body | CodeFile[] | — | Files are inputs the host already put in some session. |
files[].id | body | string | — | ID is the file's path RELATIVE to its session's artifact directory, which is also how it is fetched: GET /v1/exec/download/{session}/{id}. |
files[].name | body | string | — | Name is the display name. |
files[].session_id | body | string | — | SessionID is the other accepted spelling of the same fact on the way IN. |
files[].storage_session_id | body | string | — | StorageSessionID names the session holding the bytes, and is the spelling the answer always uses. |
lang | body | string | yes | Lang selects the toolchain, and with it the filename the code is written to and the line that runs it: py, js, ts, bash, r, php, go, rs, c, cpp, java, d, f90. |
runtime_session_hint | body | string | — | RuntimeSessionHint is the stateful-session hint. |
session_id | body | string | — | SessionID continues an EXISTING sandbox, which is what makes runs stateful: the same filesystem, so one run's output file is the next run's input. |
user_id | body | string | — | UserID attributes the run inside the caller's org. |
Response
| Status | Body | Meaning |
|---|---|---|
200 | CodeResult | ok |
200 body — 8 fields.
| Field | In | Type | Always | Description |
|---|---|---|---|---|
files | body | CodeFile[] | — | Files are what this run CREATED OR CHANGED, decided by mtime against a marker taken before the program started — so it is the run's output, not a listing of… |
files[].id | body | string | — | ID is the file's path RELATIVE to its session's artifact directory, which is also how it is fetched: GET /v1/exec/download/{session}/{id}. |
files[].name | body | string | — | Name is the display name. |
files[].session_id | body | string | — | SessionID is the other accepted spelling of the same fact on the way IN. |
files[].storage_session_id | body | string | — | StorageSessionID names the session holding the bytes, and is the spelling the answer always uses. |
session_id | body | string | — | SessionID is the sandbox this run used — the one that was passed in, or the fresh one that was leased. |
stderr | body | string | — | Stderr is what the program wrote to standard error, INCLUDING a compiler's diagnostics and the trace of a program that exited non-zero. |
stdout | body | string | — | Stdout is what the program wrote to standard output. |
Failure carries the platform error shape — see Errors.
Examples
hanzo exec create \
--code <code> \
--lang <lang>import { Configuration, ExecApi } from 'hanzoai';
const api = new ExecApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.postExec({ lang: "<lang>", code: "<code>" });from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import ExecApi
client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = ExecApi(client).post_exec(lang="<lang>", code="<code>")cfg := cloud.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := cloud.NewAPIClient(cfg)
resp, _, err := client.ExecAPI.PostExec(context.Background()).Execute()
if err != nil {
return err
}use hanzo_cloud::apis::{configuration::Configuration, exec_api};
let mut cfg = Configuration::new();
cfg.bearer_access_token = std::env::var("HANZO_API_KEY").ok();
let result = exec_api::post_exec(&cfg, Default::default()).await?;import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.ExecApi;
ApiClient client = new ApiClient();
client.setRequestInterceptor(b -> b.header("Authorization", "Bearer " + System.getenv("HANZO_API_KEY")));
var result = new ExecApi(client).postExec();curl -X POST https://api.hanzo.ai/v1/exec \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"lang": "<lang>",
"code": "<code>"
}'The door reaches exec through the exec tool, which names its 5 operations with its own verbs — this one among them, under a name only the door declares. describe explains any of them:
curl -X POST https://api.hanzo.ai/v1/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "describe",
"arguments": {
"op": "get_download"
}
}
}'How is this guide?