Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Daytona Python SDK

The official Python SDK for Daytona sandboxes. Import module and package name are both daytona.

Alpha. This SDK is published as alpha prereleases. Pin the exact version you want (see the release history on PyPI) — a bare pip install --pre daytona picks the highest prerelease on PyPI, which is not necessarily the newest alpha. The API may still change between alphas.

Install

pip install daytona==X.Y.ZaN          # substitute the alpha you want
pip install 'daytona[otel]==X.Y.ZaN'  # adds OpenTelemetry request spans
pip install 'daytona[v0-sdk]==X.Y.ZaN'    # adds the v0 namespace (Daytona().v0)

The legacy package name daytona-sdk (daytona_sdk) is published only from the 0.x line: installing or upgrading it never gives you 2.x. Install daytona.

Python 3.10+.

Quickstart

Daytona is the synchronous client. AsyncDaytona is the asynchronous one. Every operation goes through a version namespace: d.v2 for the current API, or d.v0 for the previous Daytona API (requires daytona[v0-sdk]; see v0 API below).

Resource types (Sandbox, Image, ImageBuilder, …) live in daytona.v2 or daytona.v0. Importing them from the package root raises an ImportError that names where to import from. The clients are the other way round: the root is the only way to build one (Daytona().v2, Daytona().v0), and neither namespace exports Daytona, AsyncDaytona or DaytonaConfig (from daytona.v2 import Daytona raises an ImportError naming the root form). To annotate a stored client, import its type from the root: DaytonaV2Client / AsyncDaytonaV2Client (and DaytonaV0Client / AsyncDaytonaV0Client, which need the v0-sdk extra).

from daytona import Daytona, DaytonaConfig

with Daytona(DaytonaConfig(api_key="dtn_...")) as d:
    d.v2.images.pull("python:3.12", name="py312")  # waits until ready
    sandbox = d.v2.create("py312", wait_seconds=60)
    # create() returns the handle even if its wait ended before the sandbox started
    if sandbox.state != "started":
        sandbox.wait()
    result = sandbox.exec("python -V")
    print(result.exit_code, result.output)
    d.v2.delete(sandbox)

images.pull() waits until ready by default. Set wait=False to skip the readiness wait, or timeout=120 for a 120-second budget (None, 0 or a value above about 24.8 days is unbounded). A failed import raises ImageBuildFailedError with the server's error message. images.build() remains non-waiting. Both take the same image options (name, region, cpu_arch, idle_expiry_seconds), and their submission is an ordinary request, bounded by request_timeout.

with Daytona(...) as d: releases the client's connection pools when the block ends (also on cancellation). A short script may instead let the interpreter finish — teardown drops the sockets. In a long-lived process, prefer one Daytona for the process's lifetime, or close each one you create: a client you drop without closing keeps its idle keep-alive connections open until interpreter exit.

d.v2 on the sync Daytona runs the async implementation on a private background event loop and blocks the calling thread. Calling it from inside a running event loop raises immediately instead of silently stalling that loop — use AsyncDaytona there. d.v0 keeps the 0.x SDK's own behaviour: its sync calls run (and block) wherever they are made, as they always did; from async code use AsyncDaytona().v0. A client created before fork() must not be used in the child; construct a new one.

The root owns its namespace clients: close the root (with Daytona(...) or root.close()), not root.v0 / root.v2 directly; a namespace client closed on its own stays cached and fails on later calls. Closing is optional. with Daytona(...) or close() releases the v2 client's HTTP pools and stops its loop thread right away, and closes the v0 client's event socket (and its OpenTelemetry provider, with otel_enabled); a client you never close is shut down the same way at interpreter exit, so a short script can simply fall off the end. Close explicitly in long-running processes that create many clients, or when you need the connections gone now. Waits share one WebSocket per regional API endpoint among clients with matching connection settings and events configuration on the same event loop; closing the last of them closes it.

AsyncDaytona

AsyncDaytona is the same SDK with coroutines instead of blocking calls. It returns AsyncSandbox, AsyncProcessHandle, and so on — the async twin of every type Daytona returns.

Fan out the way an orchestrator does — every handle is cheap and shares the client's connection pool:

import asyncio

from daytona import AsyncDaytona, DaytonaConfig


async def main() -> None:
    async with AsyncDaytona(DaytonaConfig(api_key="dtn_...")) as d:
        sandboxes = await asyncio.gather(*(d.v2.create_from_snapshot("worker-snapshot") for _ in range(100)))
        # Each handle comes back even if its wait ended before the sandbox started:
        # wait for the rest, and keep going when some end in a terminal state.
        await asyncio.gather(
            *(sb.wait() for sb in sandboxes if sb.state != "started"), return_exceptions=True
        )
        for sb in sandboxes:
            if sb.state != "started":
                print(f"{sb.id} did not start: {sb.state} {sb.error or ''}")
        ready = [sb for sb in sandboxes if sb.state == "started"]
        results = await asyncio.gather(*(sb.exec("python train_step.py") for sb in ready))


asyncio.run(main())

async with releases the client's connection pools when the block ends.

DaytonaConfig reads DAYTONA_API_KEY and DAYTONA_JWT_TOKEN from the environment when the fields are omitted. Both credentials serve d.v0 and d.v2. Environment values are trimmed, and a blank one counts as unset; values you pass in DaytonaConfig are used as given, and an empty one (api_key="", …) raises BadRequestError instead of falling back to the environment. Daytona(config) copies the config when it is built: changing the object afterwards has no effect on the client. Additional env vars:

  • DAYTONA_ORGANIZATION_ID — scopes a JWT for the v0 namespace.
  • DAYTONA_PROJECT_ID — scopes a JWT for the v2 namespace.
  • DAYTONA_V2_API_URL — selects another v2 API endpoint (a regional endpoint, as daytona region list in the CLI shows them, or a test environment). When unset, the SDK connects to the Daytona API. d.v0 always uses its own production URL. SDK 2.x never reads DAYTONA_API_URL, the 0.x SDK's variable.
  • Credentials: d.v2 reads DAYTONA_* variables from the process environment only. d.v0 keeps the 0.x SDK's behaviour of also reading .env.local / .env, so a key kept only in a .env file reaches d.v0 but not d.v2; export it, or pass api_key= in DaytonaConfig.
  • DAYTONA_OTEL_ENABLED — enable client spans when daytona[otel] is installed.

Set request_timeout= (seconds; default 60; 0, or a value above 2,147,483.647 (about 24.8 days), means no limit) to change the total time one ordinary request may take before it raises ClientTimeoutError. Waits and long polls use their own budget, streams are bounded only until their response arrives, and file transfers have no total limit but fail with ClientTimeoutError once no bytes move for that long (with request_timeout=0 they are not bounded at all). Set max_response_bytes= (an integer; None keeps the 1 GiB default, 0 means no limit, and any other value is a BadRequestError) to cap every response body the SDK holds in memory whole: a larger body raises PayloadTooLargeError before it is read in full (an error response keeps its status's error). Set connection_pool_maxsize= to cap both live and keep-alive HTTP connections, and otel_enabled=True to emit client spans when daytona[otel] is installed. The extra brings only opentelemetry-api; spans are exported only once your application configures an OpenTelemetry SDK tracer provider (and exporter) — the SDK never installs one itself.

Use exactly one authentication mode:

  • api_key="dtn_..." for a project API key.
  • jwt_token="...", project_id="..." to exchange a signed-in user's JWT for a short-lived project-scoped platform token automatically.

The target field on DaytonaConfig is deprecated and applies only to d.v0: pass target= to each v0 sandbox create (CreateSandboxFromSnapshotParams(target="eu")) or region_id= to each v0 snapshot create instead. For v2, pass region= to each create.

v0 API

d.v0 gives access to the previous Daytona API through the 0.x Daytona SDK, vendored into this package at a pinned release. It requires the v0-sdk extra — accessing it without the extra raises ImportError naming the install command. A program that only uses d.v2 never imports v0 code.

pip install 'daytona[v0-sdk]==X.Y.ZaN'
from daytona import Daytona, DaytonaConfig
from daytona.v0 import Sandbox  # resource types live in the namespace

with Daytona(DaytonaConfig(api_key="dtn_...")) as d:
    sandboxes = d.v0.list()

The root client is the only way to build a v0 client: daytona.v0 exports the resource types and v0's own errors, but not Daytona, AsyncDaytona or DaytonaConfig. from daytona.v0 import Daytona raises an ImportError that names the root form.

Names that exist in both namespaces are also exported with a version suffix, so a single file can use both without renaming:

from daytona.v0 import Sandbox as SandboxV0  # or import SandboxV0 directly
from daytona.v2 import Sandbox as SandboxV2  # or import SandboxV2 directly

Migrating from v0:

v0 code SDK 2.x
pip install daytona pip install "daytona[v0-sdk]"
daytona.create(...) daytona.v0.create(...)
from daytona import Sandbox from daytona.v0 import Sandbox
from daytona import Daytona then Daytona() from daytona import Daytona then Daytona().v0 (not daytona.v0.Daytona, which SDK 2.x does not export)
DaytonaConfig(api_url=...) / DAYTONA_API_URL for a custom API URL stay on SDK 0.x: SDK 2.x targets Daytona Cloud and never reads DAYTONA_API_URL

Creating without an image

d.v2.create() with no image boots the platform default image, a Linux image Daytona publishes for every project. d.v2.images.list(platform="true") lists the platform images; the default one has default=True. A platform image can also be passed by name, for example d.v2.create("daytona-base"). When no default is configured, the create raises DEFAULT_IMAGE_NOT_CONFIGURED (400).

Regions and safe create retries

d.v2.create() accepts an optional region= argument that creates the sandbox in a region listed by daytona region list (with -o json, each entry includes id, name, available, and optionally api_base_url). Omitting region falls back to the project's default, then the organization's, then earth, which means anywhere and may land in any geography. Every d.v2.create*() method accepts region=, including create_from_snapshot and create_from_build.

d.v2.images.pull("python:3.12", name="py312", region="eu")
sandbox = d.v2.create("py312", region="eu", wait_seconds=60)

A known region that cannot currently accept sandbox creates raises REGION_UNAVAILABLE (409). Image creates need a narrower region than earth or a broad area; the API rejects a too-broad image region with 400.

Every create*() sends an idempotency key: the one you pass as idempotency_key, else one derived from name (next section), else a random one per invocation. If the create request times out or loses its connection, SandboxCreateRequestTimeoutError / SandboxCreateConnectionError exposes idempotency_key. Retry with that key and the identical source and create arguments; do not start a fresh-key create to recover a lost response. A changed request under the same key is a 409, never a silent replay of the original parameters. Keys are scoped to the project and the API endpoint that served the create, not checked across regions; changing regions requires a new operation and key. Deleting a sandbox releases its key at delete intent, so the same key (or the same name) creates a fresh sandbox afterwards.

Names, get-or-create and find_by_name()

Sandbox names are not unique — they are display metadata — but a named create is also the handle of the one-sandbox-per-end-user pattern. Every create*(name=…):

  • stamps the name as a label, labels["name"] == name, next to any labels you pass (an explicit labels["name"] that disagrees with name= is rejected before any request);
  • derives a stable idempotency key from the name unless you pass idempotency_key. The derivation is identical in the TypeScript SDK, so a retry can use either SDK without creating a second sandbox.

A repeated create("py312", name="example-user") is therefore a get-or-create: the same sandbox comes back (its access_token only on the first call), not a second sandbox and not a 409 — v2 has no "409 on duplicate name". This is permanent behavior of name=. The exact semantics, all server-side:

  • Region-scoped. Reusing a key with a different regional API endpoint can create a second sandbox; use the same region and API endpoint for retries.
  • Exact. create("py312", name="example-user", resources=Resources(cpu=2)) followed by create("py312", name="example-user", resources=Resources(cpu=8)) is a 409, not a silent replay of the 2-CPU sandbox. Change the arguments and you need a new name or an explicit key.
  • Released on delete. Delete the sandbox and create(..., name="example-user") makes a new one.

find_by_name(name, region=…) returns all sandboxes carrying that exact name label, newest first, as a tuple (names are not unique); destroyed/destroying ones are left out. It is the one list method that is not a single page. It reads sandbox search with an exact name label match, in the region a create with the same region lands in (region defaults to none, i.e., the server's default, exactly as on create*()). A new sandbox appears in search shortly after its create, so a lookup right after a create can miss it — the get-or-create below stays safe, because the repeat create derives the same key and returns the same sandbox. A deployment without search raises SearchNotConfiguredError (501). Together:

existing = d.v2.find_by_name("example-user")
sandbox = existing[0] if existing else d.v2.create("py312", name="example-user")
# Two callers racing on an empty result still get ONE sandbox: both creates
# use the same name and arguments, so the second is a replay of the first.

On AsyncDaytona the same pattern uses await:

existing = await d.v2.find_by_name("example-user")
sandbox = existing[0] if existing else await d.v2.create("py312", name="example-user")

What you get

Follow build logs

images.build() returns the submitted Image without waiting for the build. Follow its output with images.follow_build_logs(image.id, on_logs=...), or just wait for the result with images.get(image.id, wait="ready", timeout=...):

from daytona.v2 import BuildLogInterruptedError, ImageBuilder

image = d.v2.images.build(ImageBuilder.from_dockerfile("FROM python:3.12\nRUN python -V\n"), name="my-worker")
try:
    result = d.v2.images.follow_build_logs(
        image.id,
        on_logs=lambda chunk: print(chunk, end="", flush=True),
        timeout=600,  # seconds; zero/None adds no observation deadline
    )
    print(result.status, result.reason, result.output_complete)
except BuildLogInterruptedError as error:
    print("Observation interrupted; last checkpoint:", error.cursor)

# For an existing sandbox's build:
result = d.v2.get("sandbox-id").follow_build_logs(
    on_logs=lambda chunk: print(chunk, end="", flush=True),
)

on_logs is required. Optional on_event receives a typed BuildLogEvent union (event.type selects the variant), including states, gaps and source unavailability. Without it, these diagnostics appear as [build logs: …] messages in on_logs. Either callback may be a plain function or return an awaitable, which is awaited before the next event (on AsyncDaytona). Build failure is a result, not a connection error. output_complete describes the requested cursor-to-end interval; gaps keep it false even after reconnection. sandbox.build_logs() remains a one-shot string of currently retained output, using the same discovered URL as the follower (await sandbox.build_logs() on AsyncDaytona). One-shot reads reject responses exceeding 2 MiB of received body bytes with BUILD_LOG_PROTOCOL_ERROR, rather than silently truncating them.

A deadline in a state event is server execution metadata, not a client abort: the follower still drains final output and waits for done with reason deadline. Your timeout remains an independent observation limit. request_timeout bounds discovery, one-shot reads, and error-response bodies, including after headers arrive; it does not limit a healthy live log stream. Each live stream uses a connection of its own, so a long observation never holds up the client's other requests.

Pass cursor=... to resume a saved checkpoint. Automatic reconnects preserve partial UTF-8 characters; a fresh follower starting mid-character can emit a replacement character. Persist consumed output and its event cursor together if you need application-level restart safety. Callback exceptions stop without retry, since their side effects may already have happened. A timeout, cancellation or keyboard interrupt closes the observation, not the build. Source changes never restart from zero automatically. Unavailable output is reported as an unavailable event; queued builds continue to be observed until completion or a deadline.

Cancelling the task that awaits follow_build_logs propagates as ordinary asyncio.CancelledError, so timeouts and task groups keep working. On Daytona, sandbox.follow_build_logs(...) and d.v2.images.follow_build_logs(...) block until the build finishes, and a keyboard interrupt is surfaced as BuildLogInterruptedError with code="BUILD_LOG_CANCELED" so the last checkpoint remains available.

Surface overview

  • Daytona client (sync) and AsyncDaytona (async) — sandbox lifecycle through d.v2: create, get, list, iter, start, stop, pause, resume, delete. Both expose the same methods; the examples above use the sync form.
  • Sandbox search — d.v2.list(cursor=, limit=) returns one read-your-writes page (GET /sandboxes) without filters, and d.v2.iter(cursor=, limit=) walks those pages. d.v2.search(...) queries the eventually consistent, all-region search API (GET /sandboxes/search) and carries every filter: states, sandbox_class, region, labels, name_prefix / id_prefix, resource/timestamp bounds, sort, order, ... states takes the SDK's sandbox states (states=["started", "paused"]); "unknown" is not searchable. Timestamp filters are created_start_at / created_end_at, updated_start_at / updated_end_at, and last_activity_start_at / last_activity_end_at; starts are inclusive and ends are exclusive.
  • Sandbox (sync) / AsyncSandbox (async) — a handle with fork(), wait() and the process, fs, git, interpreter, pty, ports, volumes, lsp, docker and computer_use namespaces. Sandbox handles borrow the client's shared transport; they do not need closing.
  • Network policy (Linux and macOS sandboxes; the API rejects unsupported sandbox configurations) — create-time network_policy on create and create_macos, plus sandbox.get_network_policy(), sandbox.update_network_policy(network_policy=..., if_match=...), and sandbox.reset_network_policy(if_match=...) over GET|PUT /sandboxes/{id}/network-policy. Responses expose desired vs applied state, the latest quoted ETag, pending or failed apply state, and the applied_start_id of the sandbox start that last applied the policy. Writes are full replacement only; reset is an explicit JSON null; the SDK does not auto-GET or auto-retry around stale If-Match. Domain allow-lists imply default-deny: omitting block_all while setting allow_domains lets the API normalize it to true, while an explicit block_all=False is rejected locally with NETWORK_POLICY_DOMAINS_REQUIRE_BLOCK_ALL. block_all=True with allow_cidrs and no allow_domains is the CIDR-only mode, which is not a DNS exfiltration boundary: DNS A lookups for any name are still sent to DNS resolvers and only answers inside allow_cidrs come back, so a sandbox can carry data out in the names it looks up. When lookups must be contained, set allow_domains — allow_domains=[] resolves no tenant name (a domain list, even an empty one, also denies TCP 80/443 to allow_cidrs unless the name is listed); block_all=True with no allow list refuses every lookup except platform-managed names.
  • Runtime-managed sandbox secrets (supported Linux sandbox configurations) — sandbox.get_secrets() and sandbox.replace_secrets(secrets=[...], if_match=...) over GET|PUT /sandboxes/{id}/secrets. Responses expose desired secret names, desired/applied revisions, the quoted ETag, the apply status (pending/applied/failed) with its error, and capture metadata. Writes are full replacement only, use caller-supplied If-Match, and never send or return secret values.
  • Platform namespaces — d.v2.images, d.v2.snapshots, d.v2.volumes, d.v2.secrets, d.v2.reservations, d.v2.mac_models.

Sandbox metadata and helpers

Every fetched sandbox exposes its metadata, read-only: labels, cpu / cpu_max (cores; 1 core = 2 vCPUs; a macOS core is one Mac CPU), memory / memory_max, disk / disk_max (GiB, exactly the API's MiB / 1024), sandbox_class / raw_sandbox_class, region, persistent, spot, spot_evicted_at, lifecycle_policy, forked_from, user, and error (network policy is read through get_network_policy()). Update it with methods such as set_labels() and reload it with refresh_data(). A class this SDK does not know reads as sandbox_class == "unknown" with the API's value in raw_sandbox_class. sandbox.access_token is the sandbox-scoped token the handle authenticates with (from the create response, a token you pass as d.v2.get(id, access_token=...), which the handle never replaces, or one it minted); it is None until one is known and never serialized.

sandbox.to_json() returns a SandboxHandleJSON (exported from daytona.v2): the handle's metadata as camelCase JSON, exactly what the TypeScript SDK's toJSON() writes for the same sandbox, timestamps kept as the server's strings, without credentials or URLs. It's a snapshot, not a way back: reconnect with d.v2.get(...) using the sandbox id it carries.

Resource models are frozen, keyword-only dataclasses: for example, images expose sandbox_class, layer_count and available_in; snapshots expose sandbox_class / raw_sandbox_class and memory_bytes; volumes expose size_gib, used_bytes and read-only options. An image's source is a frozen RegistryImageSource, DockerfileImageSource, NamedImageSource or ManifestImageSource, told apart by source.type. Closed SDK enums map unrecognized values to "unknown" and keep the original in a raw_… sibling. Timestamps are timezone-aware datetime values; a budget period start is a date. List and search pages are immutable: items is a tuple.

Use sandbox.set_labels({...}) to replace the entire label set ({} clears it), and sandbox.entrypoint_logs() to read the sandbox's entrypoint-session log as plain text. On AsyncDaytona prefix these with await.

Use sandbox.update_env({...}, unset=(...)) to update the environment inherited by processes, PTYs and sessions started afterwards. Running processes keep their current environment, and unset removes only values previously set through update_env(), not image-level environment entries.

Volumes

Manage volumes with d.v2.volumes.list(), iter(), get(), create() and delete(). create(name=...) and get(name, create=True) let the server choose the volume's type. Only create() takes type and size_gib: pass type="disk" (sized with size_gib) or type="bucket" to choose it. id_only=True on get() or delete(name_or_id) disables name fallback. Deletion returns when the API accepts the request, not when deletion finishes.

from daytona.v2 import VolumeMount

volume = d.v2.volumes.get("data", create=True)
sandbox = d.v2.create(
    "py312",
    volumes=[VolumeMount(volume_id=volume.id, mount_path="/mnt/data", subpath="project")],
)

create_from_build(..., volumes=[...]) accepts the same mounts. To mount an S3 bucket you own, create a bucket volume for it in the dashboard or with daytona volume create NAME --type bucket --bucket BUCKET_NAME --access-key-secret ACCESS_KEY_SECRET --secret-key-secret SECRET_KEY_SECRET (the last two name project secrets holding the bucket's keys), then attach it through volumes. VolumeMount(..., read_only=True) is supported for bucket volumes; disk volumes reject it.

create_macos(..., volumes=[...]) accepts disk volumes read-write. A macOS sandbox with volumes cannot use persistent=True; bucket volumes and read-only mounts raise BadRequestError.

Pull an attached volume

result = sandbox.volumes.pull(
    "volume-id",
    request_id="refresh-42",
    timeout=5,
)  # server wait in seconds, 0–30; default 30

On AsyncDaytona use await sandbox.volumes.pull(...).

This is separate from the platform d.v2.volumes management service. IDs contain 1–128 ASCII letters, digits, underscores or hyphens. PullVolumeResult has request_id, status (pulled, unchanged, pending, or unknown) and nullable manifest_id; raw_status preserves the server's status string. Fractional seconds round down to whole milliseconds, but any positive timeout waits at least 1 ms. Zero timeout submits a new request, or returns a known request's last outcome without retrying it; expiry does not cancel accepted work. Retry a pending result, or a caught ServiceUnavailableError / RateLimitError, with the same request_id and a timeout above zero. The SDK never retries automatically or generates an identity. Rate-limit errors expose request_id and retry_after_ms when supplied by the server.

Availability depends on server support: a server may return ServiceUnavailableError. unchanged does not assert that local files have no edits.

Create sources

d.v2.create() is the fast path: pass a registered image name or an img_… id and the SDK sends one POST /sandboxes request with your wait_seconds budget.

d.v2.images.pull("python:3.12", name="py312")
linux = d.v2.create("py312", wait_seconds=60)
snapshot = d.v2.create_from_snapshot("worker-after-setup", wait_seconds=60)

d.v2.create_from_build() is the slow path: it builds an anonymous image first, always waits for that image to become ready, then creates the sandbox from the image id. The build submission is an ordinary request, bounded by request_timeout; build_timeout then bounds only the build wait (seconds; None, 0 or a value above about 24.8 days means no deadline). A failed build (status failed or error) raises ImageBuildFailedError and a build that outlasts build_timeout raises RequestTimeoutError; in both cases no sandbox exists. The create then gets the full, independent wait_seconds budget and takes the same keywords as create().

from daytona.v2 import ImageBuilder, Resources

builder = ImageBuilder.from_registry("python:3.12").run_commands("pip install numpy")
sandbox = d.v2.create_from_build(
    builder, resources=Resources(cpu=2, memory=4), build_timeout=600, wait_seconds=60
)

wait_seconds is the create wait budget in whole seconds (default 60). The server holds create for up to its own short cap, then the SDK waits for the remainder. wait_seconds=0 returns as soon as the create is accepted. Once the create request has succeeded, every create*() returns the handle however its wait ends — the budget runs out, the sandbox reaches a terminal state, or a read during the wait fails: check sandbox.state and sandbox.error before doing work. A raised create or fork means the request itself failed, except for SandboxCreateRequestTimeoutError and SandboxCreateConnectionError (they carry the idempotency key because a sandbox may exist), caller cancellation, and an unexpected non-SDK error during the wait. Retry those two with their idempotency_key; a cancelled create may already have created a sandbox. create_timeout_seconds is the server-side queue/boot budget in seconds (an integer, at least -1); -1 asks the server to apply its maximum.

Linux sizing uses one resources=Resources(cpu=, cpu_max=, memory=, memory_max=, disk=, disk_max=) entry: CPU in cores, memory and disk in GiB that must convert to a whole number of MiB (value * 1024), else BadRequestError. Sizes read back exactly. create_from_snapshot() inherits the snapshot's configuration and takes no resources, CPU selectors, environment, volumes, secrets or network policy.

images.get(id, wait="ready", timeout=120) and snapshots.get(id, wait="ready", timeout=120) wait for the artifact to settle (ready, failed or error): they repeat the server's long poll, each hold at most 60 s (the wait budget governs it, not request_timeout), until it settles, and return it for you to check its status. A budget that runs out first raises RequestTimeoutError; a budget above the timer maximum (about 24.8 days) waits without a deadline. Without a timeout (omitted or 0), one long poll of the server's default length returns what the server reports. sandbox.capture_image(name=..., idle_expiry_seconds=...) captures an image from a sandbox.

macOS sandboxes

A macOS sandbox runs on a dedicated Mac reserved by your organization. Create one with d.v2.create_macos(image, ...) (POST /sandboxes/macos); create() makes Linux sandboxes. image is a macOS image name such as base-macos. resources=MacosResources(cpu=, memory=, disk=) sizes the sandbox: cpu is an integer number of physical cores (at least 4), memory is GiB (at least 4) and disk is GiB (at least 80: the whole sandbox disk, macOS included); each omitted value uses half of the Mac running the sandbox, rounded down, on a model that runs two sandboxes, or all of it on a model that runs one. The size is fixed when the sandbox is allocated and reported from then on (sandbox.memory and sandbox.disk in exact GiB, sandbox.reservation_id for the reservation). name, idempotency_key, wait_seconds and create_timeout_seconds work as on create(): a named create derives its idempotency key from the name and stamps labels["name"].

Creating a macOS sandbox, starting a stopped one, reserving a Mac or renewing a reservation, and turning a reservation's auto-renew on require macOS sandbox access for your organization: without it the API answers 403 FEATURE_NOT_ENABLED (raised as FeatureNotEnabledError). Reading sandboxes and reservations, turning auto-renew off, pause() and resume() do not require this access.

Find available regions and their regional API endpoints with daytona region list. Use d.v2.mac_models.catalog(region="<region>") or daytona mac-model list --region '<region>' to discover available Mac models and current prices. Replace <region> and <model> in the examples with values from these catalogs. When using a regional API endpoint, set DAYTONA_V2_API_URL before creating the client. Against an API that does not serve the region, create_macos raises the API's ConflictError; the SDK does not send the create anywhere else.

A sandbox without a size uses the model's default sandbox size:

from daytona.v2 import MacosResources

d.v2.create_macos("base-macos")
# Or choose resources explicitly:
d.v2.create_macos("base-macos", resources=MacosResources(cpu=4, memory=8, disk=100))

A reservation reserves one physical Mac of a named model. Set CPU, memory, and disk sizes when creating each sandbox. d.v2.mac_models.list() lists each model's resources, sandbox disk room, max_sandboxes, and default_size. Omitted sizes use half of each resource on a model that runs two sandboxes, or all of it on a model that runs one. Disk defaults round down to a whole GiB. Reserve a Mac, then create a sandbox using the model's default size:

reservation = d.v2.reservations.create(region="<region>", model="<model>")
d.v2.create_macos("base-macos", region="<region>", reservation=reservation.id, persistent=True)

Stop and pause need a persistent sandbox (persistent=True). Stop keeps the disk on the same Mac, and start and resume use that Mac. Delete erases the disk. At reservation end, a persistent sandbox's disk is saved as an anonymous image before the Mac is erased, and an ephemeral sandbox's disk is erased unsaved; a lost Mac saves persistent disks if it comes back within 7 days. If a Mac restarts before it is declared lost, a running persistent sandbox comes back stopped with its disk, a running ephemeral sandbox is lost, and stopped and paused sandboxes are unchanged. Create a sandbox from an image to continue on another Mac.

reservation picks the Mac reservation. Omitted, the sandbox goes on any reservation of your organization in the region with room for its size, or, when none has room and your organization's auto-reserve setting is on, the server buys a reservation of the cheapest model that fits. reservation="new" always buys such a reservation for the sandbox (auto_renew is accepted only here). A reservation ID targets that reservation. Acceptance does not reserve room; the sandbox can wait for room or time out. Reservations are billed per 24-hour period; without model, reservations.create() reserves the region's default model and never upgrades an unavailable model to another one.

d.v2.mac_models.catalog(region=...) (GET /mac-catalog) shows, per model, how many Macs a purchase can reserve now (available, advisory) and the price of one reservation (None when pricing is unavailable in the region). reservations.create_bulk() reserves several Macs of one model in one request: all of them, or none if fewer are free, unless min_count accepts fewer. Every purchase and renewal carries an idempotency key; pass your own so a retry after a lost response neither reserves more Macs nor pays for a second period:

catalog = d.v2.mac_models.catalog(region="<region>")
batch = d.v2.reservations.create_bulk(
    region="<region>", model="<model>", count=3, idempotency_key="nightly-macs"
)
print(f"reserved {batch.reserved} of {batch.requested} Macs")
d.v2.reservations.renew(reservation.id, idempotency_key=f"renew-{reservation.id}-2")

The dashboard's desktop view opens the Mac's screen through your authenticated dashboard session. Click once in the desktop before typing, because macOS drops keys for the first few seconds of each connection. Customers cannot end a Mac reservation before its 24-hour period.

Without a reservation to use, a macOS create fails with ConflictError and code == "NO_ACTIVE_RESERVATION" (or NO_FREE_SLOT when no reservation has room for the size; MAC_DISK_FULL when the reservations with room have no free disk space; AUTO_RESERVE_LIMIT_REACHED when auto-reserve holds its maximum number of reservations; or NO_MAC_AVAILABLE when buying a reservation, with reservation="new" or through auto-reserve, finds no fitting Mac in the region). MAC_DISK_FULL never buys a reservation. Starting a stopped macOS sandbox uses its own disk on the same Mac and reservation; it never buys a reservation. Reservations report model, capacity, resources_used, origin (explicit or auto), auto_renew and renewal_error; reservations.update(id, auto_renew=...) changes renewal on a live reservation.

pause() and resume() work on persistent macOS sandboxes. pause() saves the sandbox's memory on its Mac and waits for paused; resume() puts the sandbox in assigned and waits for started. A paused sandbox keeps its Mac slot, but pausing stops its reservation from auto-renewing: a reservation that holds only paused sandboxes lapses at the end of its period. After a resume, sandbox.mac_boot_mode reports how the sandbox resumed; sandbox.error provides error details when available.

A macOS fork() copies the disk only: omit memory or pass memory=False (memory=True answers NOT_SUPPORTED_FOR_CLASS). A fork of a running macOS sandbox boots with a fresh machine identity; a fork of a paused or stopped one rests stopped. A fork needs available capacity for its size; insufficient capacity can answer NO_FREE_SLOT or MAC_DISK_FULL. fork(wait_seconds=…) (default 60; 0 returns once accepted) waits until the macOS fork is started or stopped.

Processes

sandbox.exec() is the root alias of process.run(). process.run() executes a command and returns a ProcessResult: stdout, stderr, output, the log frames it read them from (logs), and how the process ended (reason, exit_code, signal). exit_code and signal are set only when the sandbox reported them; reason is always set ("unknown" with the value on raw_reason for one this release does not know). run() reads the output back from the process log, so its keep_logs= takes "on_exit_ttl" (default) or "until_cleanup", not "none".

process.start() returns a detached ProcessHandle with logs(), stream_logs(), wait(), write_stdin(), close_stdin(), signal-aware kill(), delete() and resize(). write_stdin() / close_stdin() are for exec and PTY handles only. stream_logs() yields one StreamLogEvent per server event of a supported type (an event of another type is skipped), each carrying its resume cursor, and stops after the eof event: StreamLogFrameEvent (a LogFrame on .frame), StreamLogStateEvent (the process ended), StreamLogWarningEvent (older frames were evicted) and StreamLogEofEvent; a stream error raises its typed error. The sandbox ends the stream with eof, so an iteration that ends normally has the whole log; a stream cut short, or one that ends without eof, raises ConnectionError: call stream_logs(cursor=...) again with the last event's cursor. timeout= is the caller wait budget (seconds), and timeout=0 means wait unbounded. execution_timeout= is the process deadline (seconds) for server-managed runs that support one; kill(escalate_after=) is in seconds too. Signals are bare names ("TERM"), in kill() and in what wait() and get() report.

Handles come from the SDK, never from a constructor: handle.to_json() returns a ProcessHandleJSON (the sandbox and process ids, plus the process kind when it is known; the same JSON the TypeScript SDK writes), and sandbox.process.connect(value) reconnects it, or a bare process id, on any worker without a request. Reconnected kind='code' handles remain replay/control handles (logs() / wait() / kill()), not a way to recover a parsed InterpreterResult on another worker.

pty.start(command=None, *, terminal=TerminalSize(cols=..., rows=...), ...) starts an interactive terminal (80×24 when terminal is omitted); it also takes cwd, env, name, user, shell, login, stdin (default "open"), execution_timeout and keep_logs (default "until_cleanup"). pty.list() is the PTY-only view of process.list(kind="pty"), and pty.delete(id) kills then cleans up that PTY record.

On AsyncDaytona, process.start() returns AsyncProcessHandle and methods that return results require await.

Sessions

Create a Session for one persistent shell. Commands run one at a time, in order; cd, exports and functions carry over. Set cwd and env at creation, not on individual commands. The context manager deletes the session and its commands on exit:

with sandbox.process.create_session(cwd="/app", env={"CI": "1"}) as session:
    session.run("export BUILD_MODE=test")
    result = session.run("npm test", execution_timeout=600)
    command = session.start("npm run dev")
    command.kill(signal="INT")  # Stops this command; the session survives.
    print(session.get().cwd)  # The shell's current directory.

With AsyncDaytona, use await sandbox.process.create_session(), async with, and await AsyncSession network methods. timeout bounds the caller's wait; execution_timeout stops the command without ending the session (both seconds); a command that has not stopped 5 seconds after its timeout ends the session instead of blocking it. Use session.to_json() (a SessionJSON) and sandbox.process.connect_session(identity) to reconnect without a request. process.list(kind="session") lists sessions; process.list(session_id=session.id) lists their commands. Sessions survive pause/resume but not a sandbox stop/start. In dash, BusyBox sh and mksh, an interrupt leaves only the innermost shell function or sourced file; bash and zsh leave the whole command (bash older than 4.4, macOS's /bin/bash, only when no DEBUG trap is set).

Tags

Group independently started processes with an optional tag, a safe identifier of 1–128 characters using ASCII letters, digits, ., _ and - (not . or ..):

await sandbox.process.start("python -m api_server", tag="job-123")
await sandbox.process.start(argv=["node", "worker.js"], tag="job-123")
shell = await sandbox.process.create_session(cwd="/app", tag="job-123")
await shell.run("nohup ./watcher.sh &")  # Commands inherit the session's tag.

await sandbox.process.list(tag="job-123")
killed = await sandbox.process.kill_all(tag="job-123")
# Returns once everything the kill covered has stopped. The tag can be reused.

handle = await sandbox.process.start("./start-server.sh")  # Backgrounds a server, exits.
await handle.wait()
await handle.kill()  # Linux with process containment: also stops the server.

These examples use AsyncDaytona; omit await with Daytona. Tags also work with process.run(), sandbox.exec() and pty.start(). A tag is immutable; session commands inherit it, and interpreter code executions cannot be tagged. ProcessInfo.tag exposes it. kill_all() keeps records and logs and returns the ids of the tagged processes that were running, or had running descendants, when it began; survivors of deleted or expired records are killed too but not listed. Its timeout is in seconds (more than 0 and at most 60, fractions allowed, default 10). A tag or timeout outside those rules raises BadRequestError before any request. ProcessKillIncompleteError means the timeout expired before all work stopped, and retrying is safe. A start racing the kill either precedes it and is killed, or starts afterwards and keeps running.

Linux and Windows support tags, filtering and tag-wide kills. macOS supports tags and filtering only: kill_all() raises UnsupportedOperationError, as it does on sandboxes without process containment. On Linux with containment, kill() also stops detached descendants (setsid, nohup, double fork) and survivors of an exited process; macOS kill() is unchanged. Tags can be reused immediately after kill_all().

Files

read_text / write_text, read_bytes / write_bytes, upload_file() / download_file(), bulk read_many / write_many, remove(), grep() (file contents) / glob() (file names), watch_dir() for directory watches, and presigned_download_url / presigned_upload_url for direct transfers. The platform mints the link; expires_in_seconds (default 3600) must be between 1 and SIGNED_FILE_MAX_TTL_SECONDS (7 days) — links always expire. sandbox.rotate_signing_key() retires every pre-signed URL minted so far; new URLs use the new key, which is never returned to the client. list(), grep(), glob() and replace() return one ListResult page (of FileInfo, FileMatch, path strings and ReplaceResult, iterable over its items); these methods have no page-draining iterator and take no cursor argument. read_text() decodes strictly as UTF-8 and keeps a leading byte-order mark.

watch_dir(path, on_event, *, recursive=False, on_overflow=None, on_exit=None) calls on_event for each change (a FilesystemEvent; timestamp_ms is Unix milliseconds; type is "create", "write", "remove", "rename", "chmod" or a kind a later sandbox adds, passed through as its string). Callbacks may be plain or coroutine functions; they run one at a time and are awaited. A failing on_event or on_overflow ends the watch with that error, which exit_error / wait_for_exit() report and on_exit receives; a failing on_exit is ignored. A watch whose stream drops ends with ConnectionError. on_exit runs exactly once for a watch watch_dir() returned; a failure before the watch starts (a refused connection, an HTTP error, a stream that ends before start) is raised from watch_dir() and does not call it. stop() returns once a callback that is already running, and on_exit, have finished.

To stop a watch from an event, signal the code that holds the handle (an asyncio.Event, or a threading.Event with the synchronous client) and call stop() there; this works in every case. As a shorthand, after a direct await watch_dir(...) a callback (or on_exit, or a task it started) can call stop() on the returned handle itself: callbacks are held until watch_dir() has returned its handle. When watch_dir() runs inside another task (asyncio.gather, asyncio.create_task, or asyncio.wait_for on Python 3.10–3.11), or with the synchronous client, the first events can reach the callback before the caller holds the handle.

With the synchronous Daytona client the callbacks run on the SDK's event-loop thread. There a callback may call its own watch's stop() (which returns without waiting), wait_for_exit() and exit_error. Any other synchronous SDK call, including another watch's methods, raises a DaytonaError (source SDK, no code), which ends the watch.

read_bytes(), read_text(), read_many() and the computer-use recording download() hold the whole response in memory: they take max_response_bytes= to replace the client's cap for one call (None, the default, keeps the client's configured cap, 1 GiB unless set; 0 means no limit; any other value, a negative, fractional or oversized number, a boolean or a string included, is a BadRequestError). For large files, or content from a sandbox you don't trust, use download_file(), which streams to disk with progress and cancellation (for a recording, pass its file_path, not its id).

upload_file() streams a local file in chunks, reports on_progress(bytes, total), accepts cancel= via any event-like object with is_set(), and can set atomic=True to write through a temp file + rename. download_file() streams to a unique temp file in the destination directory, renames it on success, reports progress with Content-Length when the server sends it, and removes that temp file on error or cancellation. write_bytes(), write_text(), and write_many() also accept atomic=True. Only with atomic=True, on a filesystem that cannot rename atomically, do write_bytes() / write_text() / upload_file() raise AtomicUploadUnsupportedError; write_many(atomic=True) keeps its partial-success contract and reports those entries in the returned errors (the failure message names the file and the atomic-rename limitation).

Interpreter

sandbox.run_code() is the root alias of interpreter.run(). Runs without an explicit context share one per-language interpreter state inside the sandbox, and that state's env is fixed when it is first created. Pass isolated=True for a throwaway run or use create_context(language=..., context_id=None, cwd=None, env=None) / connect_context() / list_contexts() for an independent named context. Select a context with run(..., context=...): a handle, its to_json() identity (an InterpreterContextJSON) or a context id with language=. A context's run(code, *, cwd=None, timeout=None) takes no env: set it when the context is created. If an explicit named context vanished, interpreter.run(..., context=...) / context.run() surface the typed SessionNotFoundError; recreate the context yourself and decide what to rerun. timeout= is enforced client-side (seconds): on expiry the kernel is interrupted. Results are split into results[], logs{stdout,stderr}, error, and process. That rich result shape comes from the interpreter run path itself; reconnecting a serialized code-process handle later does not recreate it.

Git

clone(), pull() and push() use a dedicated 30-minute remote-operation deadline by default, and each accepts a per-call timeout= override (seconds): one total deadline for the whole request (0 means no limit, as for every timeout). clone() also accepts depth= for shallow clones and insecure_skip_tls=True for one clone operation. pull() and push() accept branch= and remote=; push() also accepts set_upstream=True. commit(path, message, *, author=..., email=...) keeps the identity fields keyword-only. Pass allow_empty=True to create an empty commit when Git would otherwise refuse it. status() returns a GitStatus; history(), list_remotes() and list_branches() return one ListResult page of GitCommit, GitRemote and branch-name items. A clone, pull or push timeout= bounds the whole request.

Ports, LSP, computer use

ports owns both listener discovery and preview exposure: list(), is_in_use(port), expose(port, public=...) (also changes the visibility of an exposed port), preview_link(port), signed_preview_link(port, expires_in_seconds=...), expire_signed_preview_link(port, token), paginated list_endpoints(...), and iter_endpoints(...). To skip the preview warning page when a headless browser opens a preview URL, send {PREVIEW_WARNING_SKIP_HEADER: PREVIEW_WARNING_SKIP_HEADER_VALUE} (both exported from daytona.v2) with its requests. lsp.start(language_id, path_to_project) returns an LspServer handle with open_document(uri), close_document(uri), completions(uri, position, context=LspCompletionContext(...)), document_symbols(uri), workspace_symbols(query) and stop(). A completion item's documentation is None, a plain string or an LspMarkupContent. computer_use stays lifecycle-flat (status(), start(), stop(), restart()) and splits actions into service, mouse, keyboard, screen, display, a11y and recording. screen.screenshot() captures the whole screen or a region=ComputerUseRegion(...); any of format=, quality= or scale= returns a compressed image. a11y.tree(scope="focused") returns at most 256 levels below the root (max_depth, an integer from 0 to 256, default 256); a deeper UI comes back cut at that depth with truncated set.

Docker

Docker Engine starts automatically in sandboxes whose image ships it; there is no create option. sandbox.docker.status() returns its state (ready, starting, failed, not_present, disabled, unmanaged, deferred or unsupported), who owns it, a reason, and the process_id whose logs hold Docker's output. sandbox.docker.wait_until_ready(timeout=60) polls until the state is ready and returns the status. It raises DockerNotReadyError (error.status) as soon as Docker settles in a state that cannot become ready (failed, not_present, disabled, unmanaged, or unsupported), and DockerWaitTimeoutError when the budget (seconds) runs out while Docker is still starting or deferred, or a status request is still unanswered at the deadline. timeout=0, or a value above about 24.8 days, waits without a deadline. A Docker failure never makes the sandbox itself unusable.

Set DAYTONA_DOCKER_START in the sandbox env to choose how Docker starts: auto (default; start the image's dockerd when it ships one), entrypoint (the image's own entrypoint starts Docker) or off (Docker is not started; state disabled). If Docker status is unavailable, status() and wait_until_ready() raise NotFoundError (404).

Some environment variable names are reserved for the platform, among them every DAYTONA_* name other than DAYTONA_SANDBOX_TOKEN, DAYTONA_API_URL, DAYTONA_OTEL_ENDPOINT and DAYTONA_DOCKER_START. A sandbox env that sets a reserved name fails the create with BadRequestError (400) naming the key; the same rule applies to the keys an update_env() call sets or unsets.

Waiting for lifecycle states

Use sandbox.wait(until="started", timeout=120) to wait for a public lifecycle state. until also accepts a list of states. The default is started, with a 60-second budget; timeout=0, or a value above about 24.8 days (the timer maximum), waits without a deadline. Running out raises RequestTimeoutError; a terminal state (destroying, destroyed, error) reached first raises SandboxTerminalStateError, with the observed public state as error.state and the wire status as error.raw_state. When the server explains that state, the explanation is error.reason and ends the error message.

start(), stop(), pause(), resume() and delete() take timeout= (seconds) with the same default and unbounded form. They wait for completion: stop() returns at stopped, and delete() returns at destroyed (a sandbox disappearing during deletion also means completion). Client methods accept a sandbox or its id and the same options, for example d.v2.stop(sandbox.id, timeout=120). Once the target state has been observed, a failed metadata refresh afterwards does not turn that success into an error. The server decides which lifecycle actions the sandbox supports.

fork(wait_seconds=60) has its own wait budget; fractions are allowed, 0 skips waiting, and a value above about 24.8 days waits with no deadline; the fork's detail read counts against a finite budget. Like create, fork returns its handle once the fork request succeeded, however the wait ends. A running Linux source produces a running fork; otherwise the fork is paused if the source was paused and memory was retained, or stopped. macOS forks copy only disk and settle at started or stopped.

sandbox.delete() and d.v2.delete(sandbox) wait until the sandbox is destroyed (a 404 while waiting counts as destroyed). A sandbox with volumes attached stays destroying until the platform has finished with its volumes: usually seconds, but minutes when a lot of volume data is written at the end, so give delete(timeout=600) a budget that covers it. If that takes too long, the sandbox reports error and the wait raises SandboxTerminalStateError; when the server explains why the deletion did not finish, that is its reason. Such a sandbox can still reach destroyed later. To keep waiting for it, catch the error, pause for a few seconds and call sandbox.wait(until="destroyed", timeout=600): while the sandbox still reports error, the call raises again at once.

Deleting the image or snapshot immediately afterwards can still raise ConflictError (409) while references are being released. A missing sandbox during wait() raises NotFoundError; only delete() maps a 404 to destroyed.

Errors

The package root exports the error hierarchy both namespaces share: DaytonaError and one class per category — DaytonaBadRequestError, DaytonaAuthenticationError, DaytonaForbiddenError, DaytonaNotFoundError, DaytonaTimeoutError, DaytonaConflictError, DaytonaGoneError, DaytonaUnprocessableEntityError, DaytonaRateLimitError, DaytonaInternalServerError, DaytonaServiceUnavailableError and DaytonaConnectionError. They catch API errors from d.v0 and d.v2 alike, category for category. One exception: d.v0's transport failures (a refused connection, a timeout) arrive as v0's plain DaytonaError, as in the 0.x SDK, so catch them with DaytonaError, not DaytonaConnectionError / DaytonaTimeoutError:

import daytona

try:
    d.v2.get("missing")
except daytona.DaytonaNotFoundError as e:  # also catches a v0 not-found error
    print(e.status_code, e.code)

The v2 classes (from daytona.v2 import NotFoundError, …) have the same names as in the TypeScript SDK: BadRequestError, AuthenticationError (401), ForbiddenError (403), NotFoundError, ConflictError, RateLimitError, RequestTimeoutError (408), ClientTimeoutError (the client's own request timeout), ConnectionError, … They expose status_code, code, source and trace_id, and have code-specific subclasses (ProcessNotFoundError, SandboxTerminalStateError, …) so you can catch as narrowly as you need. code and source are open vocabularies: a code this release does not know is kept as sent and the error is the class for its HTTP status; daytona.v2.codes lists the known codes per source (codes.API_ERROR_CODES, codes.CODE_STATUS, codes.is_known_error_code, …). Import the v2 errors by name rather than with from daytona.v2 import *: ConnectionError and FileNotFoundError are the SDK's categories and would shadow Python's built-ins. Version-specific errors are imported from daytona.v2 (or daytona.v0).

The standard errors' constructors take the message plus keyword-only fields (status_code, code, source, trace_id, …), so tests can build the errors your code handles. A few specialised errors build their own message from positional identity arguments instead: SandboxTerminalStateError(sandbox_id, state, *, raw_state=None, reason=None), SandboxCreateTimeoutError(sandbox_id), SandboxCreateRequestTimeoutError(idempotency_key), SandboxCreateConnectionError(idempotency_key, detail), DockerWaitTimeoutError(status) (status may be None) and DockerNotReadyError(status).

A request refused because your organization has hit one of its limits (for example the maximum number of concurrent sandboxes) raises LimitExceededError — a RateLimitError that also carries the limit's name and counts as limit, current and max. Other rate-limited requests keep raising plain RateLimitError.

Versioning and releases

See docs/VERSIONING.md and CHANGELOG.md in the repository.

Metadata

Release files for daytona 2.0.0a9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for daytona 2.0.0a9
File Size Uploaded Provenance Actions
daytona-2.0.0a9.tar.gz 1.0 MB With provenance Details for daytona-2.0.0a9.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for daytona 2.0.0a9
File Interpreter ABI Platform Actions
daytona-2.0.0a9-py3-none-any.whl Python 3 none any Details for daytona-2.0.0a9-py3-none-any.whl

Total release size: 2.5 MB

Release files / daytona-2.0.0a9.tar.gz

Download URL daytona-2.0.0a9.tar.gz
Size 1.0 MB
Tags Source
SHA-256 checksum
How to use checksums
44412872eb826e14123a2403e82c6ed62d9957aeb93f0a14766c85cefe866b3d
BLAKE2b-256 checksum
How to use checksums
49569cce9390e20d1b80b492cff8da55b2ab20aa05067ee497e1d5d7b0224107
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.

Transparency log

Release files / daytona-2.0.0a9-py3-none-any.whl

Download URL daytona-2.0.0a9-py3-none-any.whl
Size 1.5 MB
Tags Python 3
SHA-256 checksum
How to use checksums
1ebbed97afb4770401a07f8c02fbbd154d1509b2b736dd7d098e38367c3a5a1a
BLAKE2b-256 checksum
How to use checksums
0e5518c331808a365a43e01d0fc503d10750cda8b015748ba090bcd9e49fe1c4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.

Transparency log

Security for daytona 2.0.0a9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for daytona-2.0.0a9.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page