Sandbox REST API v2

The Sandbox REST API creates isolated Linux runtimes, executes commands, transfers files, streams logs, and manages background processes.

Cloud API origin:

https://api.inngest.com

Every route below includes its /v2 prefix.

Authentication

Use an environment-scoped Inngest API key:

Authorization: Bearer <INNGEST_API_KEY>
curl https://api.inngest.com/v2/sandboxes \
  --header "Authorization: Bearer $INNGEST_API_KEY"

Create API keys in the Inngest dashboard. Keep them in server-side code and secret stores.

A workspace-scoped key selects its workspace automatically. With an account-scoped key, also send the environment name:

X-Inngest-Env: production

The request fails if the header is missing, does not match an environment, or matches more than one environment in the account.

The TypeScript inngest.sandboxes client uses the signing key and environment configuration attached to its Inngest client. See the TypeScript SDK reference.

JSON conventions

JSON requests:

  • use Content-Type: application/json;
  • reject unknown fields;
  • reject trailing or multiple JSON values; and
  • are capped at 1 MiB for Create, captured Exec, process Start, and Signal.

Most successful responses use:

{
  "data": {},
  "metadata": {
    "fetchedAt": "2026-07-28T19:00:00.123456789Z"
  }
}

List responses also include a top-level page.

Errors use:

{
  "errors": [
    {
      "code": "invalid_field_format",
      "message": "command must begin with an absolute executable path"
    }
  ]
}

Branch on code, not message. Live responses set Cache-Control: no-store. RFC 3339 timestamps can include nanosecond precision.

Sandbox resource

{
  "id": "b066bdf7-a57b-4d8b-a1a7-76eab1f91273",
  "name": "agent_job-42",
  "status": "RUNNING",
  "vpcId": "5f82a217-9d54-4fa8-a9be-d1dbba8a8db2",
  "imageRef": "default",
  "resources": {
    "vcpu": 2,
    "memoryMb": 2048
  },
  "createdAt": "2026-07-28T19:00:00.123456789Z",
  "startedAt": "2026-07-28T19:00:00.500000000Z",
  "endedAt": null
}
FieldMeaning
idPublic sandbox UUID
nameWorkspace-scoped active name
statusUppercase lifecycle state
vpcIdResolved workspace VPC; informational
imageRefResolved image; currently default
resources.vcpuAllocated virtual CPUs
resources.memoryMbAllocated memory in MiB
createdAtResource creation time
startedAtRuntime start time or null
endedAtTerminal time or null
errorOptional terminal error information

Possible statuses:

PENDING
STARTING
RUNNING
PAUSED
TERMINATING
TERMINATED
FAILED

Create returns only STARTING or RUNNING. Runtime operations require RUNNING.

Process resource

{
  "id": "45cef567-a28c-4109-b464-bc9c504ea900",
  "command": ["/bin/sh", "-c", "while :; do sleep 1; done"],
  "pid": 87,
  "state": "RUNNING",
  "startedAt": "2026-07-28T19:00:02.123456789Z"
}

Possible states:

STARTING
RUNNING
EXITED
KILLED
FAILED
LOST

exitCode is included only for EXITED. terminationSignal is included only for KILLED. Process metadata is held in memory and disappears with the sandbox.

Endpoints

MethodRoutePurpose
POST/v2/sandboxesCreate a sandbox
GET/v2/sandboxesList sandboxes
GET/v2/sandboxes/{sandboxId}Get a sandbox
DELETE/v2/sandboxes/{sandboxId}Destroy a sandbox
POST/v2/sandboxes/{sandboxId}/execRun a captured command
GET/v2/sandboxes/{sandboxId}/logsStream sandbox logs
PUT/v2/sandboxes/{sandboxId}/filesUpload or replace a file
GET/v2/sandboxes/{sandboxId}/filesDownload a file
POST/v2/sandboxes/{sandboxId}/processesStart a process
GET/v2/sandboxes/{sandboxId}/processesList processes
GET/v2/sandboxes/{sandboxId}/processes/{processId}Get a process
POST/v2/sandboxes/{sandboxId}/processes/{processId}/signalsSignal a process
POST/v2/sandboxes/{sandboxId}/processes/{processId}/waitWait for a process
GET/v2/sandboxes/{sandboxId}/processes/{processId}/outputRead retained output
GET/v2/sandboxes/{sandboxId}/processes/{processId}/output/streamTail and follow output

IDs are canonical lowercase UUIDs.

Create a sandbox

POST /v2/sandboxes
Content-Type: application/json
{
  "name": "agent_job-42",
  "vcpu": 2,
  "memoryMb": 2048
}
FieldValidation
nameRequired; 1–63 lowercase letters, digits, _, or -
vcpuRequired positive unsigned 32-bit integer
memoryMbRequired positive unsigned 32-bit integer

The API resolves the workspace's default egress-only VPC and default image. There is no vpcId, image, or template request field.

Response:

  • HTTP 201 with a full RUNNING sandbox; or
  • HTTP 202 with a full STARTING sandbox.
{
  "data": {
    "id": "b066bdf7-a57b-4d8b-a1a7-76eab1f91273",
    "name": "agent_job-42",
    "status": "STARTING",
    "vpcId": "5f82a217-9d54-4fa8-a9be-d1dbba8a8db2",
    "imageRef": "default",
    "resources": {
      "vcpu": 2,
      "memoryMb": 2048
    },
    "createdAt": "2026-07-28T19:00:00.123456789Z",
    "startedAt": null,
    "endedAt": null
  },
  "metadata": {
    "fetchedAt": "2026-07-28T19:00:00.200000000Z"
  }
}

Important errors:

HTTPCodeMeaning
400invalid_request or invalid_field_formatInvalid JSON, name, CPU, or memory
403access_deniedSandbox access is not enabled
409sandbox_name_takenActive sandbox already uses the name
409operation_ambiguousSandbox was created but its resource could not be confirmed
429rate_limitedRejected before Create
503compute_unavailableCompute could not accept Create

There is no HTTP Idempotency-Key. Do not retry operation_ambiguous.

List sandboxes

GET /v2/sandboxes?limit=50&cursor=<opaque>
ParameterDefaultValidation
limit50Integer from 1 through 250
cursorNoneOpaque cursor from the previous page
{
  "data": [
    {
      "id": "b066bdf7-a57b-4d8b-a1a7-76eab1f91273",
      "name": "agent_job-42",
      "status": "RUNNING",
      "vpcId": "5f82a217-9d54-4fa8-a9be-d1dbba8a8db2",
      "imageRef": "default",
      "resources": {
        "vcpu": 2,
        "memoryMb": 2048
      },
      "createdAt": "2026-07-28T19:00:00.123456789Z",
      "startedAt": "2026-07-28T19:00:00.500000000Z",
      "endedAt": null
    }
  ],
  "metadata": {
    "fetchedAt": "2026-07-28T19:00:03.000000000Z"
  },
  "page": {
    "cursor": "eyJjcmVhdGVkQXQiOi...",
    "hasMore": true,
    "limit": 50
  }
}

List is scoped to the authenticated workspace, includes retained terminal resources, and orders by createdAt DESC, then id DESC. It uses keyset pagination. Do not decode or construct cursors.

Get a sandbox

GET /v2/sandboxes/{sandboxId}

Returns HTTP 200 with the full resource. A missing or workspace-hidden resource returns:

404 sandbox_not_found

Destroy a sandbox

DELETE /v2/sandboxes/{sandboxId}
  • HTTP 202 returns the full TERMINATING resource.
  • HTTP 204 has no body when teardown completed synchronously.
  • A missing or hidden resource returns 404 sandbox_not_found.

The destroy intent is persisted before node teardown. After the intent is stored, temporary node unavailability can still return the durable TERMINATING resource.

Run a captured command

POST /v2/sandboxes/{sandboxId}/exec
Content-Type: application/json
{
  "command": ["/bin/sh", "-c", "printf 'tests passed\n'"],
  "environment": {
    "PATH": "/usr/local/bin:/usr/bin:/bin",
    "CI": "true"
  },
  "cwd": "/",
  "timeout": "5m"
}
FieldDefaultMeaning
commandRequiredArgument vector; item 0 must be absolute
environmentGuest defaultReplaces the complete environment
cwd/Working directory
timeout30sGo duration greater than zero and at most 5m

The command is not shell-parsed.

{
  "data": {
    "stdout": "dGVzdHMgcGFzc2VkCg==",
    "stderr": "",
    "encoding": "base64",
    "exitCode": 0
  },
  "metadata": {
    "fetchedAt": "2026-07-28T19:01:00.000000000Z"
  }
}

stdout and stderr are base64. A non-zero exit code still returns HTTP 200. Combined raw output is limited to 4 MiB.

HTTPCodeMeaning
409operation_ambiguousCommand may have executed
413sandbox_exec_output_too_largeOutput exceeded 4 MiB; command may have executed
504sandbox_exec_timed_outObservation timed out; command may have executed

Do not retry these automatically.

Stream sandbox logs

GET /v2/sandboxes/{sandboxId}/logs?follow=false
Accept: application/x-ndjson

follow defaults to false and accepts only true or false.

{"type":"log","stream":"STDOUT","data":"c3RhcnRlZAo=","encoding":"base64","at":"2026-07-28T19:00:00Z"}
{"type":"log","stream":"STDERR","data":"d2FybmluZwo=","encoding":"base64","at":"2026-07-28T19:00:01Z"}

The endpoint can commit HTTP 200 before upstream admission completes. An admission or live failure after that point is terminal:

{"type":"error","errors":[{"code":"compute_unavailable","message":"Compute is temporarily unavailable"}]}

Parse every frame. HTTP 200 alone does not mean the stream completed successfully.

Upload or replace a file

PUT /v2/sandboxes/{sandboxId}/files?path=/tmp/input.bin&mode=0640
Content-Type: application/octet-stream

The body is the raw file. URL-encode query values.

ParameterDefaultValidation
pathRequiredAbsolute path, at most 4096 bytes
mode0644Octal 0001 through 0777

Files are capped at 100 MiB. Upload cannot replace /, a directory, symlink, device, socket, or FIFO. A successful write uses an atomic replacement.

{
  "data": {
    "path": "/tmp/input.bin",
    "bytesWritten": 2
  },
  "metadata": {
    "fetchedAt": "2026-07-28T19:00:05.000000000Z"
  }
}

Success is HTTP 200. A connection failure after replacement may return 409 operation_ambiguous.

Download a file

GET /v2/sandboxes/{sandboxId}/files?path=/tmp/output.bin
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 1234
Cache-Control: no-store
X-Sandbox-File-Mode: 0640
Last-Modified: Tue, 28 Jul 2026 19:00:00 GMT

The body is the raw file. Only regular files up to 100 MiB are downloadable. Missing sandboxes and files use 404 sandbox_file_not_found.

A failure after HTTP 200 cannot become JSON. Treat a body shorter than Content-Length as failed.

Start a managed process

POST /v2/sandboxes/{sandboxId}/processes
Content-Type: application/json
{
  "command": ["/bin/sh", "-c", "while :; do sleep 1; done"],
  "environment": {
    "PATH": "/usr/local/bin:/usr/bin:/bin",
    "PORT": "8080"
  },
  "cwd": "/"
}

command is required. environment replaces rather than merges.

Response: HTTP 201.

{
  "data": {
    "id": "45cef567-a28c-4109-b464-bc9c504ea900",
    "command": ["/bin/sh", "-c", "while :; do sleep 1; done"],
    "pid": 87,
    "state": "RUNNING"
  },
  "metadata": {
    "fetchedAt": "2026-07-28T19:00:02.500000000Z"
  }
}

The platform generates the UUID before dispatch and never exposes internal guest handles. Start is not HTTP-idempotent. Do not retry 409 operation_ambiguous.

List managed processes

GET /v2/sandboxes/{sandboxId}/processes?limit=50&cursor=<opaque>
ParameterDefaultValidation
limit50Integer from 1 through 250
cursorNoneOpaque cursor from the previous page
{
  "data": [
    {
      "id": "45cef567-a28c-4109-b464-bc9c504ea900",
      "command": ["/bin/sh", "-c", "while :; do sleep 1; done"],
      "pid": 87,
      "state": "RUNNING",
      "startedAt": "2026-07-28T19:00:02.123456789Z"
    }
  ],
  "metadata": {
    "fetchedAt": "2026-07-28T19:00:03.000000000Z"
  },
  "page": {
    "cursor": "eyJpZCI6IjQ1Y2VmNTY3...",
    "hasMore": true,
    "limit": 50
  }
}

Pages are sorted by process UUID and exclude internal handles. Pass the cursor unchanged. When hasMore is false, cursor can be omitted.

Get a managed process

GET /v2/sandboxes/{sandboxId}/processes/{processId}

Returns HTTP 200 with one process resource. A missing process returns:

404 sandbox_process_not_found

Signal a managed process

POST /v2/sandboxes/{sandboxId}/processes/{processId}/signals
Content-Type: application/json
{
  "signal": 15,
  "includeChildren": true
}
FieldDefaultValidation
signalRequiredInteger from 1 through 64
includeChildrenfalseBoolean

Success returns HTTP 204. An already-terminal process accepts Signal as a no-op. Do not resend 409 operation_ambiguous automatically.

Wait for a managed process

POST /v2/sandboxes/{sandboxId}/processes/{processId}/wait?timeout=30s

There is no request body. timeout defaults to 30s, uses Go duration syntax, must be positive, and cannot exceed 5m.

{
  "data": {
    "id": "45cef567-a28c-4109-b464-bc9c504ea900",
    "state": "EXITED",
    "exitCode": 0
  },
  "metadata": {
    "fetchedAt": "2026-07-28T19:01:00.000000000Z"
  }
}

KILLED includes terminationSignal instead of exitCode.

504 sandbox_process_wait_timed_out stops only the observation. It does not stop or signal the process.

Read retained process output

GET /v2/sandboxes/{sandboxId}/processes/{processId}/output?tailBytes=65536

tailBytes defaults to 0, meaning all retained output. It accepts 0 through 524,288 bytes across stdout and stderr.

{
  "data": {
    "chunks": [
      {
        "stream": "STDOUT",
        "data": "c2VydmVyIHN0YXJ0ZWQK",
        "encoding": "base64",
        "at": "2026-07-28T19:00:03.000000000Z"
      },
      {
        "stream": "STDERR",
        "data": "d2FybmluZwo=",
        "encoding": "base64",
        "at": "2026-07-28T19:00:04.000000000Z"
      }
    ]
  },
  "metadata": {
    "fetchedAt": "2026-07-28T19:00:05.000000000Z"
  }
}

Chunks preserve observed ordering and arbitrary bytes. They are not lines. Tail selection preserves whole chunks.

The endpoint distinguishes:

404 sandbox_process_not_found
404 sandbox_process_output_not_retained

Tail and follow process output

GET /v2/sandboxes/{sandboxId}/processes/{processId}/output/stream?tailBytes=8192
Accept: application/x-ndjson

The endpoint always sends the retained tail and then follows live output. There is no follow parameter.

It confirms the process and retained-output ring and opens the upstream stream before committing HTTP 200. Admission failures therefore return an ordinary non-200 JSON error.

{"type":"log","stream":"STDOUT","data":"c3RhcnRlZAo=","encoding":"base64","at":"2026-07-28T19:00:03Z"}
{"type":"log","stream":"STDERR","data":"d2FybmluZwo=","encoding":"base64","at":"2026-07-28T19:00:04Z"}

A failure after HTTP 200 sends:

{"type":"error","errors":[{"code":"compute_unavailable","message":"Compute is temporarily unavailable"}]}

Fail the stream when type is error.

The stream is best effort. A slow consumer can miss chunks, and frames have no sequence number or loss marker.

Output retention

  • Approximately 512 KiB is retained per process.
  • Only the newest 32 process output rings are retained.
  • Output can be evicted while process metadata remains.
  • All output disappears with the sandbox.
  • Output is not a durable log store.

See Managed processes for application guidance and Sandbox errors and retries for the full error and retry model.