diff --git a/sandbox/index.md b/sandbox/index.md
index 37d07803..7773bc9b 100644
--- a/sandbox/index.md
+++ b/sandbox/index.md
@@ -1,6 +1,6 @@
---
title: Sandbox Overview and Capabilities
-description: Explore Sandbox creation, commands, files, pause and resume, and lifecycle events through the experimental Playground, with clear capability and resource boundaries.
+description: Create sandboxes with the E2B SDK or SandBase Console, inspect status, logs, and metrics, and explore the separate experimental Playground.
---
# Sandbox overview and capabilities
@@ -9,7 +9,9 @@ Sandbox provides isolated environments for running commands, executing code, and
[Quickstart / E2B SDK](./quickstart) ยท [Open Playground (Experimental)](./playground)
-## A five-minute walkthrough
+Use the [official E2B SDK](./quickstart) to integrate Sandbox into your application. Sign in to the [Sandbox Console](https://www.sandbase.ai/console/sandboxes) to create sandboxes and view instance status, logs, and metrics. See the [Sandbox product overview](https://www.sandbase.ai/landing/sandbox) for an introduction. The walkthrough and support table below describe the separate Experimental Playground.
+
+## A five-minute Playground walkthrough
1. Enter an [API key](/getting-started/api-keys) with Sandbox permissions. Connecting only reads templates and instances.
2. Choose an available template, acknowledge the resource notice, and create a short-lived instance.
diff --git a/sandbox/quickstart.md b/sandbox/quickstart.md
index a360da01..ec58b35d 100644
--- a/sandbox/quickstart.md
+++ b/sandbox/quickstart.md
@@ -13,6 +13,8 @@ import SandboxPromo from '../.vitepress/theme/SandboxPromo.vue'
Use the official E2B SDK with your SandBase API key to create an isolated sandbox, run a command, and read and write files. This guide targets **E2B JavaScript and Python SDK 2.51.0**.
+For browser-based creation and observation, sign in to the [Sandbox Console](https://www.sandbase.ai/console/sandboxes) to create an instance and view its status, logs, and metrics. Continue with the SDK steps below for application integration, or read the [Sandbox product overview](https://www.sandbase.ai/landing/sandbox).
+
## 1. Configure the official SDK
[Create a SandBase API key in the Console](https://www.sandbase.ai/console/keys), then configure the standard E2B environment variables. The official SDK reads these automatically in both JavaScript and Python.
diff --git a/scripts/public-sandbox-page-links.mjs b/scripts/public-sandbox-page-links.mjs
new file mode 100644
index 00000000..080778d6
--- /dev/null
+++ b/scripts/public-sandbox-page-links.mjs
@@ -0,0 +1,53 @@
+import assert from 'node:assert/strict'
+
+// These are published product pages, not API endpoints. Match the complete href
+// in Markdown, HTML or a compiled href property; never strip URL prefixes.
+const approvedPage = String.raw`https:\/\/www\.sandbase\.ai\/(?:console\/sandboxes|landing\/sandbox)`
+const markdownHref = new RegExp(String.raw`(\]\()${approvedPage}(\))`, 'g')
+const renderedHref = new RegExp(String.raw`(\bhref["']?\s*[:=]\s*)(\\?["'])${approvedPage}\2`, 'g')
+
+export const sandboxAPIPath = /\/(?:v1\/)?sandboxes?(?:\/|%7b|\{|:|\b)/i
+
+export function maskApprovedSandboxPageHrefs(content) {
+ return content
+ .replace(markdownHref, '$1/approved-product-page$2')
+ .replace(renderedHref, '$1$2/approved-product-page$2')
+}
+
+// Both source and built validators import this module, so these regressions run
+// in the existing CI commands without a separate test script.
+for (const url of [
+ 'https://www.sandbase.ai/console/sandboxes',
+ 'https://www.sandbase.ai/landing/sandbox',
+]) {
+ for (const wrap of [
+ value => `[Product](${value})`,
+ value => `Product`,
+ value => `Product`,
+ value => `({href:"${value}"})`,
+ value => `({"href":"${value}"})`,
+ value => String.raw`Product`,
+ ]) {
+ assert.doesNotMatch(maskApprovedSandboxPageHrefs(wrap(url)), sandboxAPIPath)
+ // An approved link must not exempt another API path in the same page.
+ assert.match(maskApprovedSandboxPageHrefs(`${wrap(url)} GET /v1/sandboxes`), sandboxAPIPath)
+ for (const suffix of ['/api', '/v1/sandboxes', '?next=/v1/sandboxes', '#/sandboxes']) {
+ const source = wrap(url + suffix)
+ assert.equal(maskApprovedSandboxPageHrefs(source), source)
+ if (url.includes('/sandboxes') || suffix.includes('/sandboxes')) {
+ assert.match(maskApprovedSandboxPageHrefs(source), sandboxAPIPath)
+ }
+ }
+ }
+}
+for (const source of [
+ 'GET /sandboxes', 'POST /v1/sandboxes', 'GET /sandboxes/{id}/metrics',
+ '[API](https://api.sandbase.ai/sandboxes)',
+ '[API](https://www.sandbase.ai/v1/sandboxes)',
+ 'API',
+ 'API',
+ 'Not an approved absolute href',
+ 'fetch("https://www.sandbase.ai/console/sandboxes")',
+]) {
+ assert.match(maskApprovedSandboxPageHrefs(source), sandboxAPIPath)
+}
diff --git a/scripts/validate-built-public-surface.mjs b/scripts/validate-built-public-surface.mjs
index c71fabd8..295c1f5a 100644
--- a/scripts/validate-built-public-surface.mjs
+++ b/scripts/validate-built-public-surface.mjs
@@ -1,3 +1,4 @@
+import { maskApprovedSandboxPageHrefs, sandboxAPIPath } from './public-sandbox-page-links.mjs'
import assert from 'node:assert/strict'
import { existsSync, readFileSync, readdirSync } from 'node:fs'
import path from 'node:path'
@@ -40,7 +41,7 @@ assert.match(legacySeedance, //, 'L
const forbidden = [
[/\/v1\/generations(?:\/|\b)/i, 'withdrawn generation API'],
[/\/v1\/blog\/assets(?:\/|\b)/i, 'internal Blog publishing storage API'],
- [/\/(?:v1\/)?sandboxes?(?:\/|%7b|\{|:|\b)/i, 'Sandbox API'],
+ [sandboxAPIPath, 'Sandbox API'],
[/\/events\/webhooks(?:\/|\b)/i, 'Sandbox event webhook API'],
[/\/v1\/endpoints\/[A-Za-z0-9_{}%.-]+\/mcp\b/i, 'Endpoint MCP transport'],
[/\/v1\/endpoint_runtime_profiles\b/i, 'Endpoint runtime-profile discovery'],
@@ -86,7 +87,7 @@ function inspect(directory) {
// The experimental documentation routes and their dedicated lazy UI chunk are public.
// Keep all former Sandbox API reference pages and unrelated bundles protected.
if (/^assets\/(?:chunks\/)?SandboxPlayground\.[^/]+\.js$/.test(relative)) continue
- const withoutGuideLinks = content.replace(/\/(?:docs\/)?sandbox\/(?:(?:playground|quickstart|index)(?:\.html)?)?(?=["'<\s?#])/g, '/experimental-guide')
+ const withoutGuideLinks = maskApprovedSandboxPageHrefs(content).replace(/\/(?:docs\/)?sandbox\/(?:(?:playground|quickstart|index)(?:\.html)?)?(?=["'<\s?#])/g, '/experimental-guide')
assert.doesNotMatch(withoutGuideLinks, pattern, `${relative} must not publish the hidden ${label}`)
continue
}
diff --git a/scripts/validate-public-api-surface.mjs b/scripts/validate-public-api-surface.mjs
index afc37a27..c89ed3f7 100644
--- a/scripts/validate-public-api-surface.mjs
+++ b/scripts/validate-public-api-surface.mjs
@@ -1,3 +1,4 @@
+import { maskApprovedSandboxPageHrefs, sandboxAPIPath } from './public-sandbox-page-links.mjs'
import assert from 'node:assert/strict'
import { readdirSync, readFileSync } from 'node:fs'
import path from 'node:path'
@@ -1131,7 +1132,7 @@ function inspectPublishedSources(directory) {
}
// Only the explicitly published experimental Sandbox guide/playground may link this section.
if (!['sandbox/index.md', 'sandbox/playground.md'].includes(relative)) {
- assert.doesNotMatch(content, /\/(?:v1\/)?sandboxes?(?:\/|\{|:|\b)/i, `${relative} must not expose sandbox API paths`)
+ assert.doesNotMatch(maskApprovedSandboxPageHrefs(content), sandboxAPIPath, `${relative} must not expose sandbox API paths`)
}
if (!['api-reference/endpoints/index.md', 'api-reference/endpoints/mcp.md', 'agents/services.md'].includes(relative)) {
assert.doesNotMatch(content, /\/v1\/endpoints\/[^\s`"']+\/mcp\b/i, `${relative} must not expose Endpoint MCP transport`)