---
title: Ports
description: Publish a guest port at its own https hostname — private by default, credential shown once.
---

A published port answers at its own https hostname, which reaches exactly that one port on exactly
that one machine.

## Expose

<CodeGroup>

```ts TypeScript
const preview = await machine.ports.expose(3000, { name: "dev-server" });
preview.url; // https://k3f9x2q7m1.preview.raster.sh
preview.access_url; // the one-shot credentialed link
preview.secret; // the raw secret
```

```python Python
preview = machine.ports.expose(3000, name="dev-server")
preview["url"]         # https://k3f9x2q7m1.preview.raster.sh
preview["access_url"]  # the one-shot credentialed link
preview["secret"]      # the raw secret
```

</CodeGroup>

The port is the one **inside the machine**, not a host port. Nothing has to be listening yet —
exposing before starting the server is the ordinary order, and the URL reports the machine as
unavailable until something answers:

<CodeGroup>

```ts TypeScript
const preview = await machine.ports.expose(3000);
await machine.terminal.execShell("cd /tmp/site && python3 -m http.server 3000 &");
```

```python Python
preview = machine.ports.expose(3000)
machine.terminal.exec_shell("cd /tmp/site && python3 -m http.server 3000 &")
```

</CodeGroup>

Exposing a port that is already published returns the preview that exists rather than a second
hostname for one service, so **a retry converges**.

## Private by default

<CodeGroup>

```ts TypeScript
await machine.ports.expose(3000, { access: "public" }); // no credential at all
```

```python Python
machine.ports.expose(3000, access="public")               # no credential at all
```

</CodeGroup>

`public` is a deliberate act with a deliberate name. An agent that exposes a port so a person can
look at it should have to say that is what it meant, and someone reading a list should be able to
see which of their services are on the open internet without opening each one.

### The secret is shown once

`secret` and `access_url` are present **only in the response to the call that created the
preview**. There is no way to read them afterwards — a value the product can hand back on request
is a value that lives in every log and cache the response passes through, exactly like an API key.

`access_url` trades the secret for a host-scoped cookie, which is what makes a private preview
openable in a browser at all: a person cannot set an `Authorization` header on an address bar.

## List, get, unexpose

<CodeGroup>

```ts TypeScript
for await (const p of machine.ports.list()) console.log(p.port, p.url, p.last_request_at);

const one = await machine.ports.get(preview.id);
await machine.ports.unexpose(preview.id);
```

```python Python
for p in machine.ports.list():
    print(p["port"], p["url"], p["last_request_at"])

one = machine.ports.get(preview["id"])
machine.ports.unexpose(preview["id"])
```

</CodeGroup>

On a later read, `secret` and `access_url` are null.

`unexpose` stops the URL immediately, and that hostname is **never issued again**. A hostname that
came back would resurrect every link and bookmark pointing at it, aimed at whatever that port
serves next.

## Deployment requirement

Publishing needs a preview domain configured on the deployment, with wildcard DNS and a wildcard
certificate under it. Without one the API raises `unsupported_operation` rather than handing back a
URL that resolves nowhere.

Bytes served through a preview count as egress. See [billing](/docs/reference/billing#meters).
