> ## Documentation Index
> Fetch the complete documentation index at: https://upstash-dx-3029-wake-on-request-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Public URLs

Expose ports from a box with public URLs. Each public URL maps to a specific port and can optionally require authentication.

***

## Quickstart

### Create a public URL

Start a web server inside your box and create a public URL to access it.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  import { Agent, Box } from "@upstash/box"

  const box = await Box.create({ runtime: "node" })

  // Start a web server on port 3000
  await box.exec.command("cd /work && npm install express")
  await box.files.write({
    path: "/work/server.js",
    content: `
      const express = require('express')
      const app = express()
      app.get('/', (req, res) => res.send('Hello from Box!'))
      app.listen(3000)
    `,
  })
  await box.exec.command("node /work/server.js &")

  // Create a public URL
  const publicUrl = await box.getPublicURL(3000)

  console.log(publicUrl.url)
  // → https://{BOX_ID}-3000.preview.box.upstash.com
  ```

  ```python box.py theme={"system"}
  from upstash_box import Box

  box = Box.create(runtime="node")

  # Start a web server on port 3000
  box.exec.command("cd /work && npm install express")
  box.files.write(
      path="/work/server.js",
      content="""
      const express = require('express')
      const app = express()
      app.get('/', (req, res) => res.send('Hello from Box!'))
      app.listen(3000)
    """,
  )
  box.exec.command("node /work/server.js &")

  # Create a public URL
  public_url = box.get_public_url(3000)

  print(public_url.url)
  # -> https://{BOX_ID}-3000.preview.box.upstash.com
  ```
</CodeGroup>

### Add authentication

Protect your public URL with bearer token or basic authentication.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  // With bearer token
  const publicUrl = await box.getPublicURL(3000, { bearerToken: true })

  console.log(publicUrl.token) // Use this in Authorization header
  // → "63d8b153..."

  // With basic auth
  const publicUrl = await box.getPublicURL(8080, { basicAuth: true })

  console.log(publicUrl.username) // → "user"
  console.log(publicUrl.password) // → "f0f145f0..."
  ```

  ```python box.py theme={"system"}
  # With bearer token
  public_url = box.get_public_url(3000, bearer_token=True)

  print(public_url.token)  # Use this in Authorization header
  # -> "63d8b153..."

  # With basic auth
  public_url = box.get_public_url(8080, basic_auth=True)

  print(public_url.username)  # -> "user"
  print(public_url.password)  # -> "f0f145f0..."
  ```
</CodeGroup>

***

## API

### Create Public URL

Creates a public URL that exposes a port on your box. Returns the URL and authentication credentials if requested.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  const publicUrl = await box.getPublicURL(3000)

  console.log(publicUrl.url)
  // → https://{BOX_ID}-3000.preview.box.upstash.com
  ```

  ```python box.py theme={"system"}
  public_url = box.get_public_url(3000)

  print(public_url.url)
  # -> https://{BOX_ID}-3000.preview.box.upstash.com
  ```
</CodeGroup>

#### With Bearer Token

Add `bearerToken: true` to require an authorization header when accessing the public URL.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  const publicUrl = await box.getPublicURL(3000, { bearerToken: true })

  console.log(publicUrl.token)
  // → "63d8b153..."

  // Access the public URL:
  // curl -H "Authorization: Bearer 63d8b153..." https://{BOX_ID}-3000.preview.box.upstash.com
  ```

  ```python box.py theme={"system"}
  public_url = box.get_public_url(3000, bearer_token=True)

  print(public_url.token)
  # -> "63d8b153..."

  # Access the public URL:
  # curl -H "Authorization: Bearer 63d8b153..." https://{BOX_ID}-3000.preview.box.upstash.com
  ```
</CodeGroup>

#### With Basic Authentication

Add `basicAuth: true` to require username and password when accessing the public URL.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  const publicUrl = await box.getPublicURL(8080, { basicAuth: true })

  console.log(publicUrl.username) // → "user"
  console.log(publicUrl.password) // → "f0f145f0..."

  // Access the public URL:
  // curl -u user:f0f145f0... https://{BOX_ID}-8080.preview.box.upstash.com
  ```

  ```python box.py theme={"system"}
  public_url = box.get_public_url(8080, basic_auth=True)

  print(public_url.username)  # -> "user"
  print(public_url.password)  # -> "f0f145f0..."

  # Access the public URL:
  # curl -u user:f0f145f0... https://{BOX_ID}-8080.preview.box.upstash.com
  ```
</CodeGroup>

#### With Both Auth Methods

Enable both authentication methods. Either one will work when accessing the public URL.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  const publicUrl = await box.getPublicURL(8080, { bearerToken: true, basicAuth: true })

  console.log(publicUrl.token) // → "63d8b153..."
  console.log(publicUrl.username) // → "user"
  console.log(publicUrl.password) // → "f0f145f0..."
  ```

  ```python box.py theme={"system"}
  public_url = box.get_public_url(8080, bearer_token=True, basic_auth=True)

  print(public_url.token)     # -> "63d8b153..."
  print(public_url.username)  # -> "user"
  print(public_url.password)  # -> "f0f145f0..."
  ```
</CodeGroup>

***

### List Public URLs

Get all active public URLs for this box.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  const { publicURLs } = await box.listPublicURLs()

  console.log(publicURLs)
  // [
  //   { id: "{BOX_ID}-3000", port: 3000, url: "https://{BOX_ID}-3000.preview.box.upstash.com",
  //     created_at: 1789990404, basic_auth: false, bearer_token: false },
  //   { id: "{BOX_ID}-8080", port: 8080, url: "https://{BOX_ID}-8080.preview.box.upstash.com",
  //     created_at: 1789990512, basic_auth: false, bearer_token: true },
  // ]
  ```

  ```python box.py theme={"system"}
  result = box.list_public_urls()

  print(result["public_urls"])
  # [
  #   PublicURL(url="https://{BOX_ID}-3000.preview.box.upstash.com", port=3000),
  #   PublicURL(url="https://{BOX_ID}-8080.preview.box.upstash.com", port=8080),
  # ]
  ```
</CodeGroup>

***

### Delete Public URL

Remove a public URL by port number.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  await box.deletePublicURL(3000)
  ```

  ```python box.py theme={"system"}
  box.delete_public_url(3000)
  ```
</CodeGroup>

***

## Behavior

### One Public URL Per Port

Creating a public URL for a port that already has one will overwrite the previous one, including any auth credentials.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  // First public URL
  const publicUrl1 = await box.getPublicURL(3000)

  // Second public URL overwrites the first one
  const publicUrl2 = await box.getPublicURL(3000, { bearerToken: true })

  // publicUrl1.url is no longer accessible
  ```

  ```python box.py theme={"system"}
  # First public URL
  public_url1 = box.get_public_url(3000)

  # Second public URL overwrites the first one
  public_url2 = box.get_public_url(3000, bearer_token=True)

  # public_url1.url is no longer accessible
  ```
</CodeGroup>

### Public URL Lifecycle

A public URL outlives a pause. While the box is paused, a request to the URL resumes it. See [Waking a Paused Box](#waking-a-paused-box).

Public URLs are removed when the box is deleted.

### Waking a Paused Box

An incoming HTTP request to a public URL resumes its box if the box is paused. The request is held while the box comes back and until the app's port is listening, so the caller gets the app's real response instead of an error. If the public URL requires authentication, the credentials are checked before the box is woken.

The wait is bounded at 30 seconds. If the port is not listening by then, the request returns `503` with a `Retry-After: 5` header rather than hanging: a browser gets a page that refreshes itself, and any other client gets a plain `503` to retry. The box keeps resuming in the background, so a retry usually succeeds. Anything slower than 30 seconds to start, such as a cold `npm install`, should be built into the image or a snapshot instead of left to the [init command](/box/overall/quickstart#init-command).

Set a client timeout longer than that window. Both examples below set one explicitly, because some clients default to less than a cold start takes.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  const publicUrl = await box.getPublicURL(3000)

  await box.pause()

  // This request resumes the box and returns the app's response
  const response = await fetch(publicUrl.url, { signal: AbortSignal.timeout(60_000) })
  console.log(response.status) // 200, or 503 if the app is still starting
  ```

  ```python box.py theme={"system"}
  import httpx

  public_url = box.get_public_url(3000)

  box.pause()

  # This request resumes the box and returns the app's response.
  # httpx defaults to a 5 second timeout, which a cold start can outlast.
  response = httpx.get(public_url.url, timeout=60.0)
  print(response.status_code)  # 200, or 503 if the app is still starting
  ```
</CodeGroup>

For the app to answer on its own after a resume, the box needs to start its server again. An [init command](/box/overall/quickstart#init-command) does that: the box runs it once every time the container starts, including after a resume.

### Auto-Resume

Creating a public URL on a paused box automatically resumes it.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  await box.pause()

  // This will resume the box
  const publicUrl = await box.getPublicURL(3000)
  ```

  ```python box.py theme={"system"}
  box.pause()

  # This will resume the box
  public_url = box.get_public_url(3000)
  ```
</CodeGroup>

***

## Examples

### Expose an agent-built app

Let an agent build a web app, then create a public URL to test it.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  import { Agent, Box } from "@upstash/box"

  const box = await Box.create({
    runtime: "node",
    agent: {
      harness: Agent.ClaudeCode,
      model: "anthropic/claude-opus-4-6",
      apiKey: process.env.ANTHROPIC_API_KEY,
    },
  })

  await box.agent.run({
    prompt: `
  Create a simple Express web server that:
  - Listens on port 3000
  - Has a / route that returns "Hello World"
  - Has a /health route that returns {"status": "ok"}
  - Start the server in the background
    `,
  })

  const publicUrl = await box.getPublicURL(3000)
  console.log(`Public URL available at: ${publicUrl.url}`)

  // Test the endpoints
  const response = await fetch(`${publicUrl.url}/health`)
  console.log(await response.json()) // { status: "ok" }
  ```

  ```python box.py theme={"system"}
  import os
  from upstash_box import Box, Agent

  box = Box.create(
      runtime="node",
      agent={
          "harness": Agent.CLAUDE_CODE,
          "model": "anthropic/claude-opus-4-6",
          "api_key": os.environ["ANTHROPIC_API_KEY"],
      },
  )

  box.agent.run(
      prompt="""
  Create a simple Express web server that:
  - Listens on port 3000
  - Has a / route that returns "Hello World"
  - Has a /health route that returns {"status": "ok"}
  - Start the server in the background
    """,
  )

  public_url = box.get_public_url(3000)
  print(f"Public URL available at: {public_url.url}")

  # Test the endpoints
  import httpx
  response = httpx.get(f"{public_url.url}/health")
  print(response.json())  # {"status": "ok"}
  ```
</CodeGroup>

### Share a secure public URL

Create a public URL with authentication for sharing with team members.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  import { Agent, Box } from "@upstash/box"

  const box = await Box.create({ runtime: "python" })

  // Set up a Flask app
  await box.files.write({
    path: "/work/app.py",
    content: `
  from flask import Flask
  app = Flask(__name__)

  @app.route('/')
  def home():
      return 'Internal Dashboard'

  if __name__ == '__main__':
      app.run(host='0.0.0.0', port=8080)
    `,
  })

  await box.exec.command("pip install flask && python /work/app.py &")

  // Create authenticated public URL
  const publicUrl = await box.getPublicURL(8080, { basicAuth: true })

  console.log(`Public URL: ${publicUrl.url}`)
  console.log(`Username: ${publicUrl.username}`)
  console.log(`Password: ${publicUrl.password}`)

  // Share these credentials with your team
  ```

  ```python box.py theme={"system"}
  import httpx
  from upstash_box import Box

  box = Box.create(runtime="python")

  # Set up a Flask app
  box.files.write(
      path="/work/app.py",
      content="""
  from flask import Flask
  app = Flask(__name__)

  @app.route('/')
  def home():
      return 'Internal Dashboard'

  if __name__ == '__main__':
      app.run(host='0.0.0.0', port=8080)
    """,
  )

  box.exec.command("pip install flask && python /work/app.py &")

  # Create authenticated public URL
  public_url = box.get_public_url(8080, basic_auth=True)

  print(f"Public URL: {public_url.url}")
  print(f"Username: {public_url.username}")
  print(f"Password: {public_url.password}")

  # Share these credentials with your team
  ```
</CodeGroup>

### Multi-port application

Create multiple public URLs for different services in the same box.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  import { Agent, Box } from "@upstash/box"

  const box = await Box.create({ runtime: "node" })

  // Start frontend on port 3000
  await box.files.write({
    path: "/work/frontend.js",
    content: `
  const express = require('express')
  const app = express()
  app.get('/', (req, res) => res.send('Frontend'))
  app.listen(3000)
    `,
  })

  // Start API on port 8080
  await box.files.write({
    path: "/work/api.js",
    content: `
  const express = require('express')
  const app = express()
  app.get('/api/status', (req, res) => res.json({ status: 'ok' }))
  app.listen(8080)
    `,
  })

  await box.exec.command("npm install express")
  await box.exec.command("node /work/frontend.js & node /work/api.js &")

  // Create a public URL for each service
  const frontendPublicUrl = await box.getPublicURL(3000)
  const apiPublicUrl = await box.getPublicURL(8080)

  console.log(`Frontend: ${frontendPublicUrl.url}`)
  console.log(`API: ${apiPublicUrl.url}`)
  ```

  ```python box.py theme={"system"}
  from upstash_box import Box

  box = Box.create(runtime="node")

  # Start frontend on port 3000
  box.files.write(
      path="/work/frontend.js",
      content="""
  const express = require('express')
  const app = express()
  app.get('/', (req, res) => res.send('Frontend'))
  app.listen(3000)
    """,
  )

  # Start API on port 8080
  box.files.write(
      path="/work/api.js",
      content="""
  const express = require('express')
  const app = express()
  app.get('/api/status', (req, res) => res.json({ status: 'ok' }))
  app.listen(8080)
    """,
  )

  box.exec.command("npm install express")
  box.exec.command("node /work/frontend.js & node /work/api.js &")

  # Create a public URL for each service
  frontend_public_url = box.get_public_url(3000)
  api_public_url = box.get_public_url(8080)

  print(f"Frontend: {frontend_public_url.url}")
  print(f"API: {api_public_url.url}")
  ```
</CodeGroup>

### Temporary testing public URL

Create a public URL, run tests against it, then clean up.

<CodeGroup>
  ```typescript box.ts theme={"system"}
  import { Agent, Box } from "@upstash/box"

  const box = await Box.create({ runtime: "node" })

  // Start test server
  await box.exec.command("npx http-server /work -p 3000 &")

  // Create a public URL for testing
  const publicUrl = await box.getPublicURL(3000)

  // Run your tests against the public URL
  const response = await fetch(publicUrl.url)
  console.log(response.status) // 200

  // Clean up
  await box.deletePublicURL(3000)
  await box.delete()
  ```

  ```python box.py theme={"system"}
  import httpx
  from upstash_box import Box

  box = Box.create(runtime="node")

  # Start test server
  box.exec.command("npx http-server /work -p 3000 &")

  # Create a public URL for testing
  public_url = box.get_public_url(3000)

  # Run your tests against the public URL
  response = httpx.get(public_url.url)
  print(response.status_code)  # 200

  # Clean up
  box.delete_public_url(3000)
  box.delete()
  ```
</CodeGroup>

<Note>The SDK still supports `getPreviewUrl`, `listPreviews`, and `deletePreview`, but they are deprecated aliases for `getPublicURL`, `listPublicURLs`, and `deletePublicURL`.</Note>
