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_outadditions, 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 inleaks: 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
proxyfallback needs the veris-proxy machinery that@veris-ai/e2bcarries;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, notconnect— see Reattaching.- snake_case options, and
veris=is a keyword argument rather than a key in the options object. - Both sync and async:
SandboxandAsyncSandbox, 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)
| File | Size | Uploaded | |
|---|---|---|---|
| veris_e2b-0.1.0.tar.gz | 36.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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}
|