# Preview (/docs/preview)

Daytona provides preview URLs for accessing services running in your sandboxes. Any process listening for HTTP traffic on ports `1` - `65535` can be previewed through a generated URL.

Daytona supports two types of preview URLs, each with a different authentication mechanism:

- [Standard preview URL](#standard-preview-url) uses the sandbox ID in the URL and requires a separate token for authentication. That token is sandbox-wide and cannot be revoked — use it for your own programmatic access, not for sharing
- [Signed preview URL](#signed-preview-url) embeds the authentication token directly in the URL, requiring no headers. The token is bound to a single port, expires, and can be revoked — use it when sharing access with someone else

## Authentication [#authentication]

If a sandbox has its `public` property set to `true`, preview links are publicly accessible without authentication. Otherwise, authentication is required. The authentication mechanism depends on the preview URL type.

<Callout type="note">
Standard and signed preview tokens are not interchangeable. The token from `get_preview_link()` is used as a preview access token (sent via the `x-daytona-preview-token` header). The token from `create_signed_preview_url()` is embedded in the URL itself: it cannot be used as a standard preview token, and vice versa.
</Callout>

## Standard preview URL [#standard-preview-url]

The standard preview URL includes your sandbox ID in the URL and provides a separate token for authentication.

URL structure: `https://{port}-{sandboxId}.{daytonaProxyDomain}`

Use standard preview URLs for programmatic access and API integrations where you control the HTTP headers.

<Callout type="caution" title="The standard preview token is a sandbox-wide credential">
The token returned by `get_preview_link()` is not restricted to the port you requested it for. It authenticates any port of that sandbox, including the ports that control the sandbox itself: the web terminal on `22222`, the toolbox API on `2280`, and the screen recordings dashboard on `33333`. Anyone holding it can run commands in the sandbox and read or write its filesystem.

The same token is also the credential the sandbox uses to send its logs, traces and metrics to Daytona's managed telemetry collector. Anyone holding it can submit telemetry for that sandbox. Daytona attributes every record to the sandbox the token belongs to, so the token cannot write telemetry for any other sandbox or organization.

Treat it like the sandbox's own credential rather than a per-port viewing token, and do not share it. To give someone access to a single port, use a [signed preview URL](#signed-preview-url), which is bound to one port, expires, and can be revoked.
</Callout>

A sandbox that is **stopped and started again** gets a new token, and tokens issued before that stop no longer work — call `get_preview_link()` again to obtain a fresh one. A sandbox **resumed from a paused state keeps its existing token**, and there is no way to revoke a standard preview token. Once issued, assume it stays valid for as long as the sandbox runs.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">
```python
preview_info = sandbox.get_preview_link(3000)

print(f"URL: {preview_info.url}")
print(f"Token: {preview_info.token}")

# Use with requests
import requests
response = requests.get(
    preview_info.url,
    headers={"x-daytona-preview-token": preview_info.token}
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">
```typescript
const previewInfo = await sandbox.getPreviewLink(3000);

console.log(`URL: ${previewInfo.url}`);
console.log(`Token: ${previewInfo.token}`);

// Use with fetch
const response = await fetch(previewInfo.url, {
  headers: { 'x-daytona-preview-token': previewInfo.token }
});
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">
```ruby
preview_info = sandbox.preview_url(3000)

puts "Preview link url: #{preview_info.url}"
puts "Preview link token: #{preview_info.token}"
```

</TabItem>
<TabItem label="Go" icon="seti:go">
```go
preview, err := sandbox.GetPreviewLink(ctx, 3000)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("URL: %s\n", preview.URL)
fmt.Printf("Token: %s\n", preview.Token)
```

</TabItem>
<TabItem label="API" icon="seti:json">
```bash
curl 'https://app.daytona.io/api/sandbox/{sandboxId}/ports/3000/preview-url' \
  --header 'Authorization: Bearer <API_KEY>'
```

</TabItem>
</Tabs>

### Authentication [#authentication-1]

Authenticate by sending the token in the `x-daytona-preview-token` header:

```bash
curl -H "x-daytona-preview-token: vg5c0ylmcimr8b_v1ne0u6mdnvit6gc0" \
  https://3000-sandbox-123456.proxy.daytona.work
```

## Signed preview URL [#signed-preview-url]

The signed preview URL embeds the authentication token directly in the URL, eliminating the need for separate headers. The token persists across sandbox restarts until it expires, or is revoked manually before expiry. Set a custom expiry time for the token: 

- Default: `60` seconds
- Minimum: `1` second
- Maximum: `86,400` seconds (24 hours)
- Recommended: `3600` seconds (1 hour)

<Callout type="tip">
Always set the `expires_in_seconds` parameter explicitly. The default of 60 seconds is short due to security considerations. Most use cases should use at least 3600 (1 hour).
</Callout>

URL structure: `https://{port}-{token}.{daytonaProxyDomain}`

Use signed preview URLs when sharing links with users who cannot set custom headers, embedding previews in iframes or emails, or creating time-limited shareable links.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">
```python
# Create a signed preview URL that expires in 3600 seconds (1 hour)
signed_url = sandbox.create_signed_preview_url(3000, expires_in_seconds=3600)

print(f"URL: {signed_url.url}")  # Token is embedded in the URL
print(f"Token: {signed_url.token}")  # Can be used to revoke access

# Use directly - no headers needed
import requests
response = requests.get(signed_url.url)

# Revoke the token before expiry if needed
sandbox.expire_signed_preview_url(3000, signed_url.token)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">
```typescript
// Create a signed preview URL that expires in 3600 seconds (1 hour)
const signedUrl = await sandbox.getSignedPreviewUrl(3000, 3600);

console.log(`URL: ${signedUrl.url}`);  // Token is embedded in the URL
console.log(`Token: ${signedUrl.token}`);  // Can be used to revoke access

// Use directly - no headers needed
const response = await fetch(signedUrl.url);

// Revoke the token before expiry if needed
await sandbox.expireSignedPreviewUrl(3000, signedUrl.token);
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">
```ruby
# Create a signed preview URL that expires in 3600 seconds (1 hour)
signed_url = sandbox.create_signed_preview_url(3000, 3600)

puts "URL: #{signed_url.url}"
puts "Token: #{signed_url.token}"
```

</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Create a signed preview URL that expires in 3600 seconds (1 hour)
signedPreview, err := sandbox.GetSignedPreviewLink(ctx, 3000, 3600)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("URL: %s\n", signedPreview.URL)   // Token is embedded in the URL
fmt.Printf("Token: %s\n", signedPreview.Token) // Can be used to revoke access

// Revoke the token before expiry if needed
if err := sandbox.ExpireSignedPreviewLink(ctx, 3000, signedPreview.Token); err != nil {
    log.Fatal(err)
}
```

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
daytona preview-url <sandbox-name> --port 3000 --expires 3600
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl 'https://app.daytona.io/api/sandbox/{sandboxId}/ports/3000/signed-preview-url?expiresInSeconds=3600' \
  --header 'Authorization: Bearer <API_KEY>'
```

</TabItem>
</Tabs>

### Authentication [#authentication-2]

The token is embedded in the URL itself, so no additional headers are required:

```bash
curl https://3000-<value>.proxy.daytona.work
```

<Callout type="tip">
Port `22222` is used by the [web terminal](/docs/web-terminal) to access the terminal using preview URLs.
</Callout>

## Warning page [#warning-page]

When opening a preview link in a browser for the first time, Daytona displays a warning page. This warning informs users about potential risks of visiting the preview URL and only appears when loading the link in a browser.

To skip the warning page:

- Send the `X-Daytona-Skip-Preview-Warning: true` header
- Upgrade to [Tier 3](/docs/limits)
- Deploy a [custom preview proxy](/docs/custom-preview-proxy)
