> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ns.rocks/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Preview Proxy

> Put your own reverse proxy in front of Nullspace preview targets without exposing edge tokens in browser URLs.

Custom preview proxies let you serve a machine preview through your own domain,
session model, headers, and application proxy while keeping Nullspace edge
credentials out of browser-visible URLs.

## Custom domains

The customer-run preview proxy is the supported custom-domain path for the
current launch. Put your own domain, authentication, branding, and browser
session policy in front of the marker-only upstream URL returned by Nullspace.

No managed custom preview domain inventory, DNS verification or TLS issuance
workflow is exposed by Nullspace yet. The API surface remains the preview proxy
target plus durable preview grant inventory and revocation.

## Create a proxy target

The TypeScript SDK does not expose a preview-proxy-target helper, so create
targets from the Python SDK, CLI, or HTTP API:

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  from nullspace import Machine

  machine = Machine.connect("mch_123")
  target = machine.create_preview_proxy_target(
      8080,
      expires_in_seconds=900,
      transports=["http", "websocket"],
  )

  print(target.http_url)
  print(target.websocket_url)
  print(target.token_header_name)
  print(target.required_forwarded_headers)
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace machine preview-proxy-target create mch_123 8080 \
    --expires 15m \
    --transport http \
    --transport websocket \
    --json
  ```

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  curl -X POST "${NULLSPACE_API_URL}/v1/machines/mch_123/ports/8080/preview-proxy-target" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{ "expires_in_seconds": 900, "transports": ["http", "websocket"] }'
  ```
</CodeGroup>

The returned `http_url` and `websocket_url` include
`__ns_preview_transport=header`. That marker is not a secret. The secret is the
transport-specific token value:

| Field                     | Use                                                                        |
| ------------------------- | -------------------------------------------------------------------------- |
| `http_url`                | Upstream HTTP target for your proxy.                                       |
| `websocket_url`           | Upstream WebSocket target for your proxy.                                  |
| `http_token`              | Send as `x-nullspace-preview-proxy-token` for HTTP upstream requests.      |
| `websocket_token`         | Send as `x-nullspace-preview-proxy-token` for WebSocket upstream requests. |
| `expires_at`              | Expiration for the proxy grant and returned tokens.                        |
| `traffic_access_required` | Whether private traffic access also requires a traffic token header.       |

## Forward upstream

Your proxy should authenticate the browser request using your own app controls,
then forward to the Nullspace upstream target with the preview proxy token:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
curl -fsS \
  -H "x-nullspace-preview-proxy-token: ${NULLSPACE_PREVIEW_PROXY_HTTP_TOKEN}" \
  -H "x-forwarded-host: app.example.com" \
  -H "x-forwarded-proto: https" \
  -H "x-forwarded-for: 203.0.113.10" \
  "${NULLSPACE_PREVIEW_PROXY_HTTP_URL}"
```

Forward the headers listed in `required_forwarded_headers`. Use `http_token`
only for HTTP upstream requests and `websocket_token` only for WebSocket
upgrades; tokens are scoped to the machine, port, grant, principal, expiry, and
transport.

Nullspace preserves `X-Forwarded-Host`, `X-Forwarded-Proto`, and
`X-Forwarded-For` exactly as your proxy sends them. It does not add missing
forwarded headers or append to an existing `X-Forwarded-For` chain. If the
machine service also needs a different upstream `Host`, configure
`network.mask_request_host`; that setting changes only the `Host` header sent
to the machine service and does not rewrite `X-Forwarded-*` metadata.

HTTP responses are returned to the browser as machine services emit them,
including redirects with relative `Location` values. For WebSocket routes, use
the returned `websocket_url` and `websocket_token`; HTTP proxy tokens are not
valid for WebSocket upgrades.

See the [custom preview proxy example](../examples/custom-preview-proxy) for
minimal Node/Express and Python/FastAPI proxy apps that forward HTTP,
WebSocket, private traffic, and required forwarded headers.

## Private traffic tokens

When a machine is created with `network={"allow_public_traffic": False}`, the
proxy target response reports `traffic_access_required: true` and the traffic
access header name. It does not return the traffic token value. Store the
`machine.traffic_access_token` returned at machine create time and send it
upstream as a separate secret:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
-H "x-nullspace-traffic-access-token: ${NULLSPACE_TRAFFIC_ACCESS_TOKEN}"
```

Preview proxy tokens and traffic access tokens are not interchangeable. Edge
validates them independently, and Nullspace strips both headers before the
request reaches the machine service.

## Rotation and revocation

Create a fresh proxy target before `expires_at` when your proxy needs continued
access. Proxy targets use durable preview grants, so `machine.list_preview_urls()`
includes them and `machine.revoke_preview_url(grant_id)` revokes them.

## Related

* [Preview URLs](./preview-urls)
* [Token model](./token-model)
* [Access control](./access-control)
* [WebSockets](./websockets)
* [Custom preview proxy example](../examples/custom-preview-proxy)
* [API reference](../api-reference)
