SandboxSDK & APIIngress & egress

Networking

Expose an app through HTTPS, connect over SSH, and choose which services your sandbox can reach. Use your Tangle API key with the TypeScript SDK or Python HTTP client.

Open an app preview

Start the server on 0.0.0.0 and expose its listening port. Wait for the preview to become ready before sharing its URL.

import { createInterface } from 'node:readline/promises';
import { Sandbox } from '@tangle-network/sandbox';

const apiKey = process.env.TANGLE_API_KEY;
if (!apiKey) throw new Error('Set TANGLE_API_KEY');

const tangle = new Sandbox({
  apiKey,
  baseUrl: 'https://sandbox.tangle.tools',
});
const box = await tangle.create({
  maxLifetimeSeconds: 900,
});
await box.waitFor('running');

await box.write('/workspace/index.html', '<h1>Hello from your sandbox</h1>');
await box.process.spawnExact('python3', [
  '-m', 'http.server', '3000', '--bind', '0.0.0.0', '--directory', '/workspace',
], { timeoutMs: 900_000 });
const link = await box.previewLinks.create(3000);
const ready = await box.previewLinks.waitUntilReady(link.previewId);
console.log(ready.url);

const terminal = createInterface({ input: process.stdin, output: process.stdout });
await terminal.question('Press Enter when finished viewing the preview.');
terminal.close();

await box.delete();

Preview URLs are public. The example keeps its sandbox running until you press Enter, then deletes it.

MethodUse
box.ports()Discover listening ports in the sandbox.
box.previewLinks.create(port)Create an HTTPS preview URL for a running app.
box.previewLinks.waitUntilReady(previewId)Wait for the public route to reach the app.
box.previewLinks.get(previewId)Refresh the link’s delivery status.
box.previewLinks.list()List preview links.
box.previewLinks.remove(previewId)Remove a preview link.
box.network.exposePort(port)Expose a port and return its public URL.
box.network.listUrls()Read exposed port URLs.
box.network.getConfig()Read exposed port numbers.

Connect over SSH

Create the sandbox with sshEnabled: true and supply sshPublicKeys or registered sshKeyIds. Load the matching private key into your local SSH agent. Install the Tangle CLI for the SSH tunnel:

bash
npm install -g @tangle-network/sandbox-cli

Set TANGLE_SANDBOX_ID to that sandbox’s ID. This program opens SSH and leaves the sandbox running when you disconnect.

TypeScript
import { execSync } from 'node:child_process';
import { Sandbox } from '@tangle-network/sandbox';

const apiKey = process.env.TANGLE_API_KEY;
const sandboxId = process.env.TANGLE_SANDBOX_ID;
if (!apiKey || !sandboxId) {
  throw new Error('Set TANGLE_API_KEY and TANGLE_SANDBOX_ID');
}

const tangle = new Sandbox({ apiKey, baseUrl: 'https://sandbox.tangle.tools' });
const box = await tangle.get(sandboxId);
if (!box) throw new Error('Sandbox not found');

const ssh = await box.sshCommand();
execSync(ssh.command, {
  stdio: 'inherit',
  env: { ...process.env, ...ssh.env },
});

sshCommand() supplies the SSH command and its authentication environment. ssh() returns the username, port, and proxy command separately. The tunnel runs through the Sandbox API using tangle ssh-proxy. Keep the returned authentication environment out of logs.

Register reusable keys with tangle.sshKeys.create(name, publicKey). Use tangle.sshKeys.list() and tangle.sshKeys.delete(id) to manage them.

Restrict outbound access

Set egressPolicy when creating a sandbox. This example permits requests to the documentation site:

import { Sandbox } from '@tangle-network/sandbox';

const apiKey = process.env.TANGLE_API_KEY;
if (!apiKey) throw new Error('Set TANGLE_API_KEY');

const tangle = new Sandbox({
  apiKey,
  baseUrl: 'https://sandbox.tangle.tools',
});
const box = await tangle.create({
  maxLifetimeSeconds: 900,
  egressPolicy: {
    mode: 'strict', allowDomains: ['docs.tangle.tools'],
    includeImplicitDomains: false,
  },
});
await box.waitFor('running');

const result = await box.exec(
  `node -e "fetch('https://docs.tangle.tools').then(r => console.log(r.status))"`,
);
console.log(result.stdout);

await box.delete();
ModeOutbound access
openUnrestricted internet access.
strictListed domains and the sandbox’s provisioned model endpoints.
blockedNo outbound internet access.

In strict mode, set includeImplicitDomains: false to exclude the platform’s additional default domains. Tangle applies these policies through its egress proxy. Use egressPolicy and box.egress for outbound controls; the deprecated box.network.update() rejects outbound policy fields.

Change a running sandbox’s policy

Set TANGLE_SANDBOX_ID to a sandbox you own. This example replaces its outbound policy and keeps the sandbox running. Team sandboxes require an owner or admin to change this policy.

import { Sandbox } from '@tangle-network/sandbox';

const apiKey = process.env.TANGLE_API_KEY;
const sandboxId = process.env.TANGLE_SANDBOX_ID;
if (!apiKey || !sandboxId) {
  throw new Error('Set TANGLE_API_KEY and TANGLE_SANDBOX_ID');
}

const tangle = new Sandbox({ apiKey, baseUrl: 'https://sandbox.tangle.tools' });
const box = await tangle.get(sandboxId);
if (!box) throw new Error('Sandbox not found');

console.log(await box.egress.get());
await box.egress.update({
  mode: 'strict',
  allowDomains: ['docs.tangle.tools', 'api.github.com'],
  includeImplicitDomains: false,
});
console.log(await box.egress.get());

Updating the policy restarts the egress proxy and briefly interrupts outbound connections. If the API reports RUNTIME_RELOAD_FAILED, retry the update; the saved policy has not reached the running proxy.

Inspect outbound requests

Read allowed and refused destinations for an existing sandbox:

import { Sandbox } from '@tangle-network/sandbox';

const apiKey = process.env.TANGLE_API_KEY;
const sandboxId = process.env.TANGLE_SANDBOX_ID;
if (!apiKey || !sandboxId) {
  throw new Error('Set TANGLE_API_KEY and TANGLE_SANDBOX_ID');
}

const tangle = new Sandbox({ apiKey, baseUrl: 'https://sandbox.tangle.tools' });
const box = await tangle.get(sandboxId);
if (!box) throw new Error('Sandbox not found');

console.table(await box.egress.denials({ limit: 20 }));
const activity = await box.egress.activity({ limit: 20 });
console.table(activity.destinations);
console.log({
  requestsObserved: activity.requestsObserved,
  complete: activity.complete,
  incompleteReasons: activity.incompleteReasons,
});

denials() reports refused hosts and the rule that refused them. activity() includes allowed requests, refusal counts, and whether the inspected log window is complete. Check complete and incompleteReasons before treating an empty result as evidence of no traffic.

JavaScript / TypeScriptPython HTTP endpoint
box.egress.get()GET /v1/sandboxes/{id}: egressPolicy and egressPolicySource
box.egress.update(policy)PATCH /v1/sandboxes/{id}/egress
box.egress.denials({ limit })GET /v1/sandboxes/{id}/egress/denials?limit=20
box.egress.activity({ limit })GET /v1/sandboxes/{id}/egress/activity?limit=20

activity() returns HTTP 409 when egress logging is unavailable because the sandbox has no egress proxy.