Install the SDK:
Synchronous client
from katakate import Client
k7 = Client(endpoint="https://<your-endpoint>", api_key="<your-key>")
# Create a sandbox
sb = k7.create({
"name": "my-sandbox",
"image": "alpine:latest",
# optional: "namespace": "default",
# optional: "env_file": ".env",
# optional: "egress_whitelist": ["1.1.1.1/32", "8.8.8.8/32"],
# optional: "limits": {"cpu": "1", "memory": "1Gi", "ephemeral-storage": "2Gi"},
# optional: "before_script": "apk add curl"
})
# Execute a command
result = sb.exec('echo "Hello World"')
print(result["stdout"]) # Also includes stderr and exit_code
# List sandboxes
print(k7.list())
# Delete sandbox
sb.delete()
Client configuration
endpoint
: Base URL of your API, e.g. https://<your-endpoint>
.
api_key
: Your API key. The SDK sends it via X-API-Key
automatically.
Get your endpoint and API key using the CLI: k7 api-status
, k7 get-api-endpoint
, k7 generate-api-key <name>
. See the CLI guide: /guides/cli
.
Create with non-root, capabilities, egress controls, limits
By default, all Linux capabilities are dropped. You can add back minimal ones if needed.
sb = k7.create({
"name": "secure-sb",
"image": "alpine:latest",
"namespace": "default",
# Non-root execution
"pod_non_root": True, # Pod UID/GID/FSGroup 65532
"container_non_root": True, # Container UID 65532, no privilege escalation
# Capabilities: drop ALL by default, add minimal ones back
"cap_add": ["CHOWN"],
"cap_drop": ["NET_RAW"],
# Network egress control
# - Omit key to keep egress open
# - [] blocks all egress (DNS to CoreDNS still allowed)
# - [CIDRs] allows only those CIDRs (+ DNS)
"egress_whitelist": [
"1.1.1.1/32",
"8.8.8.8/32",
"203.0.113.0/24"
],
# Resource limits/requests (same values used for both)
"limits": {"cpu": "500m", "memory": "512Mi", "ephemeral-storage": "2Gi"},
# Optional setup commands run before Ready (executed with open egress)
"before_script": "apk add --no-cache curl git"
})
env_file
points to a file on the API host filesystem (server-side), not the client machine. If you need environment variables and you’re calling a remote API, pass values directly for now.
Wait until sandbox is Ready
import time
def wait_until_ready(name: str, namespace: str = "default", timeout_seconds: int = 120) -> None:
deadline = time.time() + timeout_seconds
while time.time() < deadline:
for info in k7.list(namespace=namespace):
if info.get("name") == name and info.get("status") == "Running" and info.get("ready") == "True":
return
time.sleep(2)
raise TimeoutError("Sandbox did not become Ready in time")
wait_until_ready("secure-sb")
Execute commands and handle errors
res = sb.exec("echo hello && uname -a")
print(res["stdout"]) # command output
print(res["stderr"]) # error stream (if any)
print(res["exit_code"]) # 0 on success
# Example of a failing command
bad = sb.exec("sh -lc 'exit 2'")
if bad["exit_code"] != 0:
print("Command failed:")
print("stderr:", bad.get("stderr", ""))
List, filter by namespace
print(k7.list()) # all namespaces
print(k7.list(namespace="dev")) # only dev
Delete and delete all
k7.delete("secure-sb")
k7.delete_all(namespace="default")
Async client
import asyncio
from katakate import AsyncClient
async def main():
k7 = AsyncClient(endpoint="https://<your-endpoint>", api_key="<your-key>")
sandboxes = await k7.list()
print(sandboxes)
await k7.aclose()
asyncio.run(main())
Async examples
Create, wait, exec, delete:
import os
import asyncio
from katakate import AsyncClient
K7_ENDPOINT = os.getenv("K7_ENDPOINT")
K7_API_KEY = os.getenv("K7_API_KEY")
async def main():
try:
k7 = AsyncClient(endpoint=K7_ENDPOINT, api_key=K7_API_KEY)
cfg = {
"name": "async-sb",
"image": "alpine:latest",
"pod_non_root": True,
"container_non_root": True,
"cap_add": ["CHOWN"],
# "before_script": "apk add --no-cache curl" # This is commented out here as it would fail, because 'apk add' needs root access, which we removed with pod_non_root and container_non_root set to True
"egress_whitelist": [], # full network lockdown after the before_script
}
print("Creating sandbox...")
await k7.create(cfg)
print("Sandbox created.")
# (Optional) Simple readiness wait (poll list). This can be removed, it is just here to illustrate.
for _ in range(60):
sbs = await k7.list()
if any(s.get("name") == "async-sb" and s.get("status") == "Running" and s.get("ready") == "True" for s in sbs):
break
await asyncio.sleep(2)
out = await k7.exec("async-sb", "echo from async")
print("Output of execution:", out)
except Exception as e:
raise e
# Include a finally block to clean resources even if code fails
finally:
print("Deleting sandbox 'async-sb'...")
try:
await k7.delete("async-sb")
print("Sandbox 'async-sb' deleted.")
except:
raise Exception("Failed to delete async-sb, you might need to clean resources manually.")
print("Closing the client's httpx connection...")
try:
await k7.aclose()
print("Connection closed.)
except:
raise Exception("Failed to close the K7 client's httpx connection, you might need to clean resources manually.)
asyncio.run(main())
Errors and responses
- Successful responses are wrapped as
{ "data": ... }
by the API; the SDK unwraps them.
- Errors are returned as
{ "error": { "code": string, "message": string } }
with appropriate HTTP status codes.
Tips
- Provide a
namespace
explicitly if you use non-default namespaces.
- Keep API keys secret; rotate via
k7 revoke-api-key
and k7 generate-api-key
.