# Sandbox HTTPS URLs

An Avrea sandbox can publish HTTP servers that run inside it on public HTTPS
URLs: a dev server preview, a webhook receiver, a notebook. Each published
port gets its own URL with a trusted certificate. HTTP/1.1, HTTP/2,
WebSockets and streamed responses work.

For creation, connections and lifecycle, see [Sandboxes](/sandboxes/).

## URL format

```text
https://<label>-<port>.<datacenter>.<sandbox-domain>   guest port <port>
https://<label>.<datacenter>.<sandbox-domain>          the default port
```

- `<label>` is 20 random characters unique to the sandbox. It is not the
  sandbox ID, and Avrea publishes it in no DNS zone or certificate log.
- The default port is the first port in `https_ports`.

A sandbox keeps its URLs when it pauses and resumes. If a resume has to place
the sandbox in another datacenter, its URLs change, and the sandbox's details
show the new ones.

## Publish ports

Set `https_ports` when you create a sandbox, or change it later:

```bash
curl -X PATCH \
  -H "Authorization: Bearer avk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"https_ports": [3000, 8080]}' \
  "https://api.avrea.com/orgs/{org_id}/vms/{vm_id}"
```

- Publish up to 10 ports. Ports 22, 3389 and 5900 are reserved for SSH and
  remote desktop. Send `[]` to publish none.
- Changing the ports needs admin access to the sandbox. A running sandbox
  gets the new URLs within seconds, and the response lists them. A paused
  sandbox gets them the next time it resumes.
- The response's `https_ports_applied` is `true` when the running sandbox serves
  the new ports at the URLs the response lists, and `false` when that is not
  confirmed yet, for example while the sandbox is paused.
- A port you remove stops answering within seconds, whatever the sandbox's
  state.

While the sandbox runs, `GET /orgs/{org_id}/vms/{vm_id}` lists its URLs in
`endpoints.https`, in the order of `https_ports`:

```json
"https": [
  { "port": 3000, "url": "https://<label>-3000.<datacenter>.<sandbox-domain>" },
  { "port": 8080, "url": "https://<label>-8080.<datacenter>.<sandbox-domain>" }
]
```

In the [Avrea console](https://console.avrea.com), the sandbox's page lists them
under **HTTPS URLs**, each with buttons to open and copy it.

## Serve from the sandbox

Serve plain HTTP and listen on all interfaces (`0.0.0.0`), not only on
`localhost`. Avrea terminates HTTPS. Requests arrive with:

- `Host` set to the URL's hostname, so an app that builds absolute URLs from
  it links back to the sandbox URL;
- `X-Forwarded-For` (the client's address), `X-Forwarded-Proto: https`,
  `X-Forwarded-Host` and `X-Forwarded-Port: 443`. Forwarding headers the
  client sent (`X-Forwarded-*`, `Forwarded`, `X-Real-IP`) are removed first.

Login shells in Linux and macOS sandboxes get the URLs as environment variables:

| Variable | Value |
| --- | --- |
| `AVREA_SANDBOX_URL` | The default port's URL |
| `AVREA_SANDBOX_HOST` | The default port's hostname |
| `AVREA_SANDBOX_URL_<PORT>` | Each published port's URL, for example `AVREA_SANDBOX_URL_8080` |

Use them for allowed hosts, OAuth callback URLs and CORS origins. For a Vite
dev server published as the default port:

```js
// vite.config.js
export default {
  server: {
    host: '0.0.0.0',
    allowedHosts: [process.env.AVREA_SANDBOX_HOST],
  },
};
```

The variables are set when the sandbox boots or resumes, and updated within
seconds when its ports or URLs change while it runs. A shell opened before
the change keeps the old values; open a new login shell to get the new
ones.

### Responses from Avrea

| Status | Meaning |
| --- | --- |
| 404 | No running or paused sandbox has this URL. |
| 503 | The sandbox is paused, and the same URL answers again once it resumes; or the URL has too many open requests, or Avrea's edge is restarting. Retry after a moment. |
| 502 | Nothing listens on the port inside the sandbox, the sandbox cannot be reached, or the app returned an invalid response. |
| 429 | The URL received more requests than its rate limit allows. |

## Who can reach a URL

Sandbox URLs are public: anyone who has a URL reaches its port, without
signing in. The random label keeps a URL from being guessed, so treat it like
a secret link, and add authentication to your app for anything sensitive.
Members with read access to a sandbox see its URLs.

### Rotate a URL

If a URL leaks, rotate the sandbox's label. This replaces all of the
sandbox's URLs, and the old ones stop answering shortly after.

```bash
curl -X POST \
  -H "Authorization: Bearer avk_your_api_key" \
  "https://api.avrea.com/orgs/{org_id}/vms/{vm_id}/https-label-rotations"
```

Rotating needs write access to the sandbox, and each user can rotate 5 times a
minute. A running sandbox's response lists its new URLs; a paused sandbox
gets them when it next resumes. Inside a running sandbox, new login shells get
the new URLs in the `AVREA_SANDBOX_*` variables within seconds. A sandbox that
has never run with published ports has no URLs to rotate, and the request
answers 409.

### Turn sandbox URLs off

An organization admin can turn sandbox URLs off for the whole organization:

```bash
avr settings set vm.https.enabled false --org org-abc123
```

Every URL in the organization stops answering shortly after, and sandboxes
boot or resume without URLs. The API then refuses requests that add a port;
removing ports still works. Turned back on, each sandbox gets its URLs back at
its next resume.

## Limits

| Limit | Value |
| --- | --- |
| Published ports per sandbox | 10 |
| Reserved ports | 22, 3389 and 5900 |
| Requests per URL | 500 a second, with bursts up to 1,000, then 429 |
| Open requests per URL | 1,000, counting each WebSocket and server-sent event stream for as long as it is open, then 503 |
| Concurrent connections per client IP address | 256 |
| Concurrent HTTP/2 streams per connection | 100 |
| Request headers | 64 KiB |
| Idle keep-alive connections | Closed after 120 seconds |
| Request bodies and response time | Not limited: uploads, long polling, server-sent events and WebSockets stream for as long as they run |