> ## Documentation Index
> Fetch the complete documentation index at: https://hyperbrowser.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox Lifecycle

Sandboxes instantly launch with the configuration you need. When a sandbox is created, it is always running until you stop it or it times out.
By default, this is based on your team’s default Session Timeout setting which you can change on the Settings page.
You can also configure the timeout per sandbox during creation. `timeoutMinutes`
is the sandbox's total lifetime beginning when its VM starts, not a timeout
applied separately to each SDK operation.

For example, set a custom session timeout when creating the sandbox:

<CodeGroup>
  ```typescript Node.js theme={null}
  const sandbox = await client.sandboxes.create({
    imageName: "node",
    timeoutMinutes: 30,
  });
  ```

  ```python Python 1.0+ theme={null}
  sandbox = client.sandboxes.create(
      {
          "image_name": "python",
          "timeout_minutes": 30,
      }
  )
  ```

  ```python Python (legacy) theme={null}
  from hyperbrowser.models import CreateSandboxParams

  sandbox = client.sandboxes.create(
      CreateSandboxParams(
          image_name="python",
          timeout_minutes=30,
      )
  )
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.hyperbrowser.ai/api/sandbox \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "imageName": "node",
      "timeoutMinutes": 30
    }'
  ```
</CodeGroup>

## Getting a Sandbox

Fetch a detailed sandbox handle by ID:

<CodeGroup>
  ```typescript Node.js theme={null}
  const sandbox = await client.sandboxes.get(
    "550e8400-e29b-41d4-a716-446655440000",
  );
  const detail = sandbox.toJSON();

  // Selected fields from detail:
  // {
  //   "id": "550e8400-e29b-41d4-a716-446655440000",
  //   "teamId": "team-id",
  //   "status": "active",
  //   "endTime": null,
  //   "startTime": 1775822400000,
  //   "createdAt": "2026-04-10T12:00:00.000Z",
  //   "updatedAt": "2026-04-10T12:00:00.000Z",
  //   "region": "us-west",
  //   "sessionUrl": "https://app.hyperbrowser.ai/sandboxes/550e8400-e29b-41d4-a716-446655440000",
  //   "duration": 0,
  //   "proxyBytesUsed": 0,
  //   "cpu": 2,
  //   "memoryMiB": 2048,
  //   "diskMiB": 8192,
  //   "timeoutMinutes": 30,
  //   "runtime": {
  //     "transport": "regional_proxy",
  //     "host": "https://<regional-runtime-host>",
  //     "baseUrl": "https://<regional-runtime-host>/sandbox/<sandbox-id>"
  //   },
  //   "exposedPorts": [
  //     {
  //       "port": 3000,
  //       "auth": true,
  //       "url": "https://<exposed-port-host>/",
  //       "browserUrl": "https://<exposed-port-host>/_hb/auth?grant=<exposed-port-token>&next=%2F",
  //       "browserUrlExpiresAt": "2026-04-11T12:00:00.000Z"
  //     }
  //   ],
  //   "token": "sandbox-runtime-token",
  //   "tokenExpiresAt": "2026-04-11T12:00:00.000Z"
  // }
  ```

  ```python Python theme={null}
  sandbox = client.sandboxes.get("550e8400-e29b-41d4-a716-446655440000")
  detail = sandbox.to_dict()

  # Selected fields from detail:
  # {
  #   "id": "550e8400-e29b-41d4-a716-446655440000",
  #   "team_id": "team-id",
  #   "status": "active",
  #   "end_time": None,
  #   "start_time": 1775822400000,
  #   "created_at": "<datetime>",
  #   "updated_at": "<datetime>",
  #   "region": "us-west",
  #   "session_url": "https://app.hyperbrowser.ai/sandboxes/550e8400-e29b-41d4-a716-446655440000",
  #   "duration": 0,
  #   "proxy_bytes_used": 0,
  #   "cpu": 2,
  #   "memory_mib": 2048,
  #   "disk_mib": 8192,
  #   "timeout_minutes": 30,
  #   "runtime": {
  #     "transport": "regional_proxy",
  #     "host": "https://<regional-runtime-host>",
  #     "base_url": "https://<regional-runtime-host>/sandbox/<sandbox-id>"
  #   },
  #   "exposed_ports": [
  #     {
  #       "port": 3000,
  #       "auth": True,
  #       "url": "https://<exposed-port-host>/",
  #       "browser_url": "https://<exposed-port-host>/_hb/auth?grant=<exposed-port-token>&next=%2F",
  #       "browser_url_expires_at": "<datetime>"
  #     }
  #   ],
  #   "token": "sandbox-runtime-token",
  #   "token_expires_at": "<datetime>"
  # }
  ```

  ```bash cURL theme={null}
  curl -X GET https://api.hyperbrowser.ai/api/sandbox/550e8400-e29b-41d4-a716-446655440000 \
    -H "x-api-key: YOUR_API_KEY"

  # Abbreviated REST response:
  # {
  #   "id": "550e8400-e29b-41d4-a716-446655440000",
  #   "teamId": "team-id",
  #   "status": "active",
  #   "endTime": null,
  #   "startTime": 1775822400000,
  #   "createdAt": "2026-04-10T12:00:00.000Z",
  #   "updatedAt": "2026-04-10T12:00:00.000Z",
  #   "region": "us-west",
  #   "sessionUrl": "https://app.hyperbrowser.ai/sandboxes/550e8400-e29b-41d4-a716-446655440000",
  #   "duration": 0,
  #   "proxyBytesUsed": 0,
  #   "vcpus": 2,
  #   "memMiB": 2048,
  #   "diskSizeMiB": 8192,
  #   "timeoutMinutes": 30,
  #   "runtime": {
  #     "transport": "regional_proxy",
  #     "host": "https://<regional-runtime-host>",
  #     "baseUrl": "https://<regional-runtime-host>/sandbox/<sandbox-id>"
  #   },
  #   "exposedPorts": [
  #     {
  #       "port": 3000,
  #       "auth": true,
  #       "url": "https://<exposed-port-host>/",
  #       "browserUrl": "https://<exposed-port-host>/_hb/auth?grant=<exposed-port-token>&next=%2F",
  #       "browserUrlExpiresAt": "2026-04-11T12:00:00.000Z"
  #     }
  #   ],
  #   "token": "sandbox-runtime-token",
  #   "tokenExpiresAt": "2026-04-11T12:00:00.000Z"
  # }
  ```
</CodeGroup>

The REST API returns VM sizing fields as `vcpus`, `memMiB`, and
`diskSizeMiB`. The Node SDK exposes the same values as `cpu`, `memoryMiB`, and
`diskMiB`; the Python SDK exposes them as `cpu`, `memory_mib`, and `disk_mib`.
`duration` is the elapsed runtime in milliseconds after a sandbox ends, so it
is `0` while the sandbox is active. `timeoutMinutes` is the configured total
lifetime.

## Listing Sandboxes

List your sandboxes with optional filtering:

<CodeGroup>
  ```typescript Node.js theme={null}
  const response = await client.sandboxes.list({
    status: "active",
    page: 1,
    limit: 20,
  });

  // Selected response fields:
  // {
  //   "totalCount": 1,
  //   "page": 1,
  //   "perPage": 20,
  //   "sandboxes": [
  //     {
  //       "id": "sandbox-id",
  //       "status": "active",
  //       "region": "us-west"
  //     }
  //   ]
  // }
  ```

  ```python Python 1.0+ theme={null}
  response = client.sandboxes.list(
      {
          "status": "active",
          "page": 1,
          "limit": 20,
      }
  )

  # Selected fields from response.model_dump():
  # {
  #   "total_count": 1,
  #   "page": 1,
  #   "per_page": 20,
  #   "sandboxes": [
  #     {
  #       "id": "sandbox-id",
  #       "status": "active",
  #       "region": "us-west"
  #     }
  #   ]
  # }
  ```

  ```python Python (legacy) theme={null}
  from hyperbrowser.models import SandboxListParams

  response = client.sandboxes.list(
      SandboxListParams(
          status="active",
          page=1,
          limit=20,
      )
  )

  # Selected fields from response.model_dump():
  # {
  #   "total_count": 1,
  #   "page": 1,
  #   "per_page": 20,
  #   "sandboxes": [
  #     {
  #       "id": "sandbox-id",
  #       "status": "active",
  #       "region": "us-west"
  #     }
  #   ]
  # }
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api.hyperbrowser.ai/api/sandboxes?status=active&page=1&limit=20" \
    -H "x-api-key: YOUR_API_KEY"

  # Abbreviated REST response:
  # {
  #   "totalCount": 1,
  #   "page": 1,
  #   "perPage": 20,
  #   "sandboxes": [
  #     {
  #       "id": "550e8400-e29b-41d4-a716-446655440000",
  #       "status": "active",
  #       "region": "us-west"
  #     }
  #   ]
  # }
  ```
</CodeGroup>

## Refreshing and Connecting

Connect to an existing sandbox or refresh the runtime token.

<Note>
  Create and get responses for an active sandbox include a runtime token that
  is valid for 24 hours. The Node and Python SDKs automatically obtain a fresh
  token shortly before expiry and retry a replayable runtime HTTP request once
  with a fresh token after a `401`. Call `refresh()` when you also want to
  refresh the handle's cached sandbox metadata.
  For a more detailed guide to runtime tokens, see [Sandbox Runtime URLs](/docs/sandboxes/runtime).
</Note>

<CodeGroup>
  ```typescript Node.js theme={null}
  // Refresh an existing handle
  await sandbox.refresh();

  // Reconnect from an ID
  const reattached = await client.sandboxes.connect(
    "550e8400-e29b-41d4-a716-446655440000",
  );

  // Selected fields from reattached.toJSON():
  // {
  //   "id": "550e8400-e29b-41d4-a716-446655440000",
  //   "runtime": {
  //     "baseUrl": "https://<regional-runtime-host>/sandbox/<sandbox-id>"
  //   },
  //   "tokenExpiresAt": "<24 hours after the reconnect request>"
  // }
  ```

  ```python Python theme={null}
  # Refresh an existing handle
  sandbox.refresh()

  # Reconnect from an ID
  reattached = client.sandboxes.connect(
      "550e8400-e29b-41d4-a716-446655440000"
  )

  # Selected fields from reattached.to_dict():
  # {
  #   "id": "550e8400-e29b-41d4-a716-446655440000",
  #   "runtime": {
  #     "base_url": "https://<regional-runtime-host>/sandbox/<sandbox-id>"
  #   },
  #   "token_expires_at": "<datetime about 24 hours after reconnect>"
  # }
  ```
</CodeGroup>

## Stopping a Sandbox

Always stop a sandbox when you are done with it:

<CodeGroup>
  ```typescript Node.js theme={null}
  const response = await sandbox.stop();

  // {
  //   "success": true
  // }
  ```

  ```python Python theme={null}
  response = sandbox.stop()

  # response.model_dump()
  # {
  #   "success": True
  # }
  ```

  ```bash cURL theme={null}
  curl -X PUT https://api.hyperbrowser.ai/api/sandbox/SANDBOX_ID/stop \
    -H "x-api-key: YOUR_API_KEY"

  # {
  #   "success": true
  # }
  ```
</CodeGroup>

<Tip>
  Stopping a sandbox is safe to call more than once.
</Tip>

## Exposing Ports

Expose a port when you need a custom process to be accessible from outside from the sandbox.
This provides a custom runtime URL for that exposed port.

<CodeGroup>
  ```typescript Node.js theme={null}
  const exposure = await sandbox.expose({
    port: 3000,
    auth: true,
  });

  // {
  //   "port": 3000,
  //   "auth": true,
  //   "url": "https://<exposed-port-host>/",
  //   "browserUrl": "https://<exposed-port-host>/_hb/auth?grant=<exposed-port-token>&next=%2F",
  //   "browserUrlExpiresAt": "2026-04-11T12:00:00Z"
  // }

  // Helper for deriving the URL for an exposed port:
  // sandbox.getExposedUrl(3000)
  ```

  ```python Python 1.0+ theme={null}
  exposure = sandbox.expose(
      {
          "port": 3000,
          "auth": True,
      }
  )

  # exposure.model_dump(mode="json")
  # {
  #   "port": 3000,
  #   "auth": True,
  #   "url": "https://<exposed-port-host>/",
  #   "browser_url": "https://<exposed-port-host>/_hb/auth?grant=<exposed-port-token>&next=%2F",
  #   "browser_url_expires_at": "2026-04-11T12:00:00Z"
  # }

  # Helper for deriving the URL for an exposed port:
  # sandbox.get_exposed_url(3000)
  ```

  ```python Python (legacy) theme={null}
  from hyperbrowser.models import SandboxExposeParams

  exposure = sandbox.expose(
      SandboxExposeParams(
          port=3000,
          auth=True,
      )
  )

  # exposure.model_dump(mode="json")
  # {
  #   "port": 3000,
  #   "auth": True,
  #   "url": "https://<exposed-port-host>/",
  #   "browser_url": "https://<exposed-port-host>/_hb/auth?grant=<exposed-port-token>&next=%2F",
  #   "browser_url_expires_at": "2026-04-11T12:00:00Z"
  # }

  # Helper for deriving the URL for an exposed port:
  # sandbox.get_exposed_url(3000)
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.hyperbrowser.ai/api/sandbox/SANDBOX_ID/expose \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "port": 3000,
      "auth": true
    }'

  # {
  #   "port": 3000,
  #   "auth": true,
  #   "url": "https://<exposed-port-host>/",
  #   "browserUrl": "https://<exposed-port-host>/_hb/auth?grant=<exposed-port-token>&next=%2F",
  #   "browserUrlExpiresAt": "2026-04-11T12:00:00Z"
  # }
  ```
</CodeGroup>

If `auth` is enabled, send the sandbox bearer token when calling the exposed URL:

<CodeGroup>
  ```typescript Node.js theme={null}
  const detail = await sandbox.info();
  const healthUrl = new URL("health", exposure.url);
  const response = await fetch(healthUrl, {
    headers: {
      Authorization: `Bearer ${detail.token}`,
    },
  });

  const body = await response.text();

  console.log(response.status); // 200
  console.log(body); // <service response>
  ```

  ```python Python theme={null}
  import requests

  detail = sandbox.info()
  response = requests.get(
      f"{exposure.url.rstrip('/')}/health",
      headers={"Authorization": f"Bearer {detail.token}"},
      timeout=30,
  )
  response.raise_for_status()
  body = response.text

  print(response.status_code)  # 200
  print(body)  # <service response>
  ```

  ```bash cURL theme={null}
  curl "${EXPOSED_URL%/}/health" \
    -H "Authorization: Bearer $SANDBOX_TOKEN"
  ```
</CodeGroup>

<Note>
  For a dedicated guide to runtime URLs, exposed service URLs, browser auth
  links, and end-to-end examples, see [Sandbox Runtime URLs](/docs/sandboxes/runtime).
</Note>

<Warning>
  Port `4001` is reserved for the sandbox runtime API url and cannot be exposed.
</Warning>
