Skip to main content

Veris SDK for E2B — Python

Run your code in an E2B sandbox where calls to api.stripe.com, www.googleapis.com, and the rest of your vendor stack are answered by Veris dependency sandboxes — stateful, contract-accurate mocks — with the code under test completely unmodified.

No base-URL overrides, no injected config. Your code keeps its production hostnames, credentials, and SDKs; the network layer does the rest.

This is the Python sibling of @veris-ai/e2b. Same control plane, same contract, same option names in snake_case — see differences.

1. Install

uv add veris-e2b        # or: pip install veris-e2b

It depends on e2b, so that comes with it.

2. Get your keys

Variable Where from
E2B_API_KEY e2b.dev/dashboard
VERIS_API_KEY your Veris dashboard
VERIS_ENVIRONMENT_ID a Veris environment — it decides which vendor services your sandbox gets
export E2B_API_KEY=e2b_…
export VERIS_API_KEY=…
export VERIS_ENVIRONMENT_ID=…

3. Run code against mocked vendors

from veris_e2b import Sandbox

sbx = Sandbox.create()

# api.stripe.com is answered by your Veris mock — the code never knows.
result = sbx.commands.run("curl -sS https://api.stripe.com/v1/customers -u sk_test_veris:")
print(result.stdout)

sbx.kill()

Inside an event loop, use AsyncSandbox — same contract, awaited:

from veris_e2b import AsyncSandbox

sbx = await AsyncSandbox.create()
await sbx.commands.run("curl -sS https://api.stripe.com/v1/customers -u sk_test_veris:")
await sbx.kill()

4. Check the receipt

A test suite that quietly stopped calling its dependency prints the same output as one that works. The receipt is how you tell them apart:

from veris_e2b import TouchMatcher

# raises VerisUntouchedError unless the service actually saw a matching request
sbx.veris.assert_touched("stripe", TouchMatcher(method="POST", path="/v1/charges"))

Creating a sandbox

Every E2B option still works; the Veris options live under one veris keyword so a future e2b release cannot collide with them. Pass a VerisOpts or a plain dict.

from veris_e2b import Sandbox, VerisOpts

sbx = Sandbox.create(
    "my-template",  # any e2b template
    timeout=15 * 60,  # any e2b option, seconds
    veris=VerisOpts(
        environment_id="env_…",  # default: VERIS_ENVIRONMENT_ID
        api_key="…",  # default: VERIS_API_KEY
        api_base="https://svc.api.veris.ai",  # default: VERIS_API_BASE
        snapshot_id="snap_…",  # boot the twin from a snapshot
        attach_sandbox_id="sb_…",  # or reuse an existing twin
        egress="strict",  # "strict" | "open"
        allow_out=["pypi.org"],  # extra hosts your code may reach
        ttl_minutes=25,  # twin lifetime; default: timeout + 10
        install_ca=True,  # trust the interception CA
        data_plane_env=True,  # inject DATABASE_URL etc.
    ),
)
Option Default What it does
environment_id VERIS_ENVIRONMENT_ID Which Veris environment the mocks come from — it decides which vendor services you get.
api_key VERIS_API_KEY Veris credential.
api_base VERIS_API_BASE or https://svc.api.veris.ai Control plane to talk to.
snapshot_id — Boot the twin from one of the environment's snapshots instead of its baseline, so every run starts from the same known state. Mutually exclusive with attach_sandbox_id.
attach_sandbox_id — Attach to an existing Veris sandbox instead of creating one. kill() will not delete it.
ttl_minutes timeout + 10 Backstop lifetime for the Veris sandbox, in case teardown never runs.
egress "strict" What may leave the sandbox — see egress policy.
allow_out [] Extra hosts or CIDRs your code may reach. A hostname is interceptable; a CIDR is passed through.
install_ca True Install the interception CA into the sandbox's trust stores.
data_plane_env True Inject non-HTTP connection strings (e.g. DATABASE_URL) as env.
mode "auto" "auto" and "gateway" both mean gateway mode here — see differences.

Starting from a known state

A twin booted from the environment's baseline starts wherever that environment starts. snapshot_id pins it to a snapshot you captured earlier, so a suite, a benchmark, or a person exploring by hand all begin from the same rows:

sbx = Sandbox.create(veris=VerisOpts(environment_id="env_…", snapshot_id="snap_…"))

The snapshot must belong to that environment — the control plane refuses one that does not, and the error names the snapshot rather than the environment. It is recorded in the sandbox's E2B metadata as veris_snapshot_id, so a running sandbox can always say what state it started from.

The sbx.veris API

sbx.veris.receipt()                    # all services: counts + typed requests
sbx.veris.receipt("stripe")            # one service
sbx.veris.receipt_baseline()           # mark the log before a run
sbx.veris.receipt_since(baseline)      # only what this run did
sbx.veris.assert_touched("stripe")     # raises if it was never called
sbx.veris.control("stripe", "manual")  # read a service's control resources
sbx.veris.services()                   # what's running in this twin
sbx.veris.get_data_plane_env()         # {"DATABASE_URL": "postgresql://…"}
sbx.veris.get_trust_env()              # CA paths, for processes that scrub env
sbx.veris.deliver_to(3000)             # send webhooks to this sandbox
sbx.veris.update_network({...})        # change egress without losing interception

sbx.veris_sandbox_id                   # the Veris twin backing this sandbox
sbx.veris_mode                         # "gateway"

AsyncSandbox exposes the same names, awaited.

Receipts

receipt = sbx.veris.receipt()
receipt.services["stripe"].requests   # 3
receipt.integrity                     # "verified" — the tunnel was re-proven just now
receipt.leaks                         # [] in strict mode
receipt.services["stripe"].capped     # False — the whole log was read

integrity is "verified" only when the canary probe confirmed egress is still tunneled at read time. leaks names blind spots the current egress mode genuinely has (udp-quic-possible, ech-possible) rather than implying a receipt sees everything.

capped is the third thing to read. The log is paged, and a read that stops before the log does reports a floor, not a count — capped is then True and incomplete_reason says which limit it hit. assert_touched treats that as insufficient evidence rather than as an untouched dependency, because "we could not see them" and "it was never called" are different failures.

Run-scoped receipts

A twin you attached to already has a log. Counting all of it credits your run with traffic from before it began — so mark the log first, and read only past the mark:

baseline = sbx.veris.receipt_baseline()      # before the run
...                                          # the run
receipt = sbx.veris.receipt_since(baseline)
receipt.services["stripe"].requests          # this run's calls, and only these

The baseline is anchored by a unique control request, so it survives a reset that preserves numeric ids, and receipt_since revalidates it after reading — a reset part-way through invalidates the whole measurement rather than half of it. It is plain data (baseline.to_dict() / ReceiptBaseline.from_dict()), so a run can outlive the process that started it.

Reading and seeding a service by hand

sbx.veris.control("stripe", "manual")    # how this twin behaves
sbx.veris.control("stripe", "schema")    # its shape
sbx.veris.control("stripe", "data")      # its seed state
sbx.veris.control("stripe", "data", method="PATCH", body={...})   # change it

manual, schema, operations, data and requests are the whole surface, and only data accepts a write — everything else describes the twin rather than its contents. Lifecycle verbs are deliberately absent: you own the sandbox, not the twin's existence.

Webhooks

If your app receives callbacks, tell the mocks where to deliver them:

sbx = Sandbox.create(network={"allow_public_traffic": True})
sbx.commands.run("python app.py", background=True)  # listening on :3000

sbx.veris.deliver_to(3000)  # → https://3000-<id>.e2b.app
sbx.veris.deliver_to("https://my.tunnel.dev")  # or your own URL
sbx.veris.deliver_to(None)  # unregister
sbx.veris.deliver_to(3000, probe=False)  # skip the reachability check

deliver_to resolves the sandbox's own public URL — the address a vendor would POST to in production — registers it with every mocked service in one call, and verifies they can actually reach it before returning.

Reattaching

sbx = Sandbox.reconnect("i7x2qk9d0v3mnbhs", api_key="…")

reconnect restores the whole Veris surface from the sandbox's metadata, re-asserts egress in case a raw update dropped it, and re-proves the tunnel. It is named reconnect rather than connect because e2b's connect is also an instance method (resume this sandbox), and one name cannot mean two things.

Egress policy

  • egress="strict" (default) — only your vendor hosts, allow_out additions, and data planes may leave the sandbox. QUIC/HTTP3 and ECH fail closed, so the receipt has no known blind spots.
  • egress="open" — everything may leave (pip, npm, GitHub work with no configuration), at the cost of two blind spots the receipt annotates in leaks: a QUIC or ECH client could reach a real vendor unseen.

To change egress later without losing interception, use sbx.veris.update_network() rather than the raw e2b call — the raw one clears omitted fields and would drop the interception config.

Templates

Any E2B template works — pass it as the first argument. The image needs ca-certificates (to trust the interception CA); a template without it raises TemplateUnsupportedError rather than running half-configured.

Errors

Every error subclasses VerisError, so one except separates Veris failures from e2b's, and each carries a phase naming where it died.

Error When
MissingCredentialsError A required key or environment id is absent — raised before any network call, naming the variable.
VerisGatewayNotOfferedError The control plane does not offer gateway mode.
VerisGatewayUnreachableError The gateway is down.
ReceiptIntegrityError Interception could not be proven — a receipt read now would lie.
VerisUntouchedError assert_touched found no matching requests.
TwinExpiredError The Veris sandbox is gone (expired or deleted).
TemplateUnsupportedError The template can't host the interception CA.
UnsupportedOperationError An operation that would break the one-sandbox-one-twin invariant, e.g. fork().

Differences from the TypeScript SDK

  • Gateway mode only. The in-sandbox proxy fallback needs the veris-proxy machinery that @veris-ai/e2b carries; mode="proxy" raises rather than pretending. A control plane that does not offer the gateway is refused loudly, not silently un-intercepted — use the TypeScript package there.
  • reconnect, not connect — see Reattaching.
  • snake_case options, and veris= is a keyword argument rather than a key in the options object.
  • Both sync and async: Sandbox and AsyncSandbox, mirroring e2b's own pair.

Limitations

  • fork() is not supported. Forked sandboxes would share one twin and corrupt each other's receipts, so it raises.
  • Clients that pin their own CA bundle (some vendor SDKs ship one and ignore the system trust store) must be pointed at /etc/ssl/certs/ca-certificates.crt.
  • HTTP/2 and WebSockets on mocked hosts are not yet handled in gateway mode; HTTP/1.1 over TLS is. Non-mocked hosts are unaffected.

Development

uv sync
uv run pytest         # unit tests — mocked, no account needed
uv run ruff check .
uv run ruff format .

Releasing

uv version 0.2.0      # then uv lock, commit, PR, merge

Then Actions → release-python → Run workflow. It builds, checks the artifacts and publishes to PyPI over trusted publishing (OIDC, no token), then tags python-v0.2.0. dry_run: true rehearses everything but the publish. Versions are PEP 440 (0.2.0rc1, not 0.2.0-rc.1), and this package versions separately from the npm pair. Details in CONTRIBUTING.md.

License

Apache-2.0

Metadata

Release files for veris-e2b 0.1.0

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

Source distribution (sdist)

Source distribution for veris-e2b 0.1.0
File Size Uploaded
veris_e2b-0.1.0.tar.gz 36.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for veris-e2b 0.1.0
File Interpreter ABI Platform
veris_e2b-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 80.9 kB

Release files / veris_e2b-0.1.0.tar.gz

Download URL veris_e2b-0.1.0.tar.gz
Size 36.7 kB
Tags Source
SHA-256 checksum
How to use checksums
3a639f148be71283ccb72df9d1659f70187cdb349f09cbc545a538fce720eea7
BLAKE2b-256 checksum
How to use checksums
dd5ac6e51c061476bb7c058072eb12b2d4e2678973f199522c6ef286ef00cfb9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.16 {"installer":{"name":"uv","version":"0.12.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / veris_e2b-0.1.0-py3-none-any.whl

Download URL veris_e2b-0.1.0-py3-none-any.whl
Size 44.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4c4ab6430f6900e83a1266151e9c5424456673498a64bd95561b996aa1c8981c
BLAKE2b-256 checksum
How to use checksums
ecd65c2caf284e39a8a757163bb7c3e6692914d55b3d7b6359acb831093a3553
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.16 {"installer":{"name":"uv","version":"0.12.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.0 This release

2 release files

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