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

# Instance and preview URLs

> Reach the software running inside an instance over HTTPS, at instance and preview URLs you can derive from the instance id.

Every instance with a default port is reachable at `https://{instanceId}.agent37.app`. The instance id is the DNS label, so instance `ab12cd34ef` lives at `https://ab12cd34ef.agent37.app`: you can construct the URL from the id alone, with no lookup step.

An instance needs no public IP address. Every port it listens on has its own HTTPS URL, and the platform edge checks your key. A [signed URL](#browser-access-with-signed-urls) opens a port in a browser, a [public port](/docs/agents-api/public-ports) serves one of your own ports without any key, and a [custom domain](/docs/agents-api/domains) serves all of them under your own domain. [SSH](/docs/agents-api/ssh) takes the same path: the stream travels over a WebSocket to the instance's SSH port.

<Info>
  Instance URLs are the Agent API plane: you reach what runs *inside* an instance here, authenticated by the same `sk_live_` key as the hosting API, sent as the `X-Agent37-Key` header. See [Core concepts](/docs/agents-api/concepts).
</Info>

## Every port has a URL

Nothing is declared and nothing is looked up; both URL forms are derivable from the instance id alone:

* **Instance URL**: `https://{instanceId}.agent37.app`, the bare id. It routes to the template's [`default_port`](/docs/agents-api/templates#register-a-workspace-template) (`3737`, the gateway, unless the template says otherwise), and it is the `url` field on every instance object.
* **Preview URL**: `https://{instanceId}-{port}.agent37.app`, for **any** port, no registration needed. Preview URLs serve your own services plus the agent's built-in dashboard, terminal, and file browser.

A port nothing listens on answers with a transport error (`502`) rather than a `404`: the URL always routes; whether something serves is up to the instance.

You can also serve these on your own domain: register a [custom domain](/docs/agents-api/domains) and every URL here (the instance URL, preview URLs, and every public-port hostname) is mirrored at `{label}.yourdomain.com` with the same authentication.

### What `agent37-hermes` serves

| Port   | URL                                        | Serves                                                                                                              |
| ------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `3737` | `https://ab12cd34ef.agent37.app` (default) | The gateway: [chat](/docs/agents-api/chat) at `/v1/responses`, plus sessions, [files](/docs/agents-api/files), models, health |
| `9119` | `https://ab12cd34ef-9119.agent37.app`      | Hermes dashboard (browser)                                                                                          |
| `7681` | `https://ab12cd34ef-7681.agent37.app`      | A shell in the container (browser)                                                                                  |
| `8080` | `https://ab12cd34ef-8080.agent37.app`      | File browser for the workspace (browser)                                                                            |

So for the default [template](/docs/agents-api/templates), chat is just the bare URL plus a path: `POST https://ab12cd34ef.agent37.app/v1/responses`. The dashboard, terminal, and file browser live on preview URLs; mint a [signed URL](#browser-access-with-signed-urls) to open one in a browser.

### What `agent37-openclaw` serves

| Port    | URL                                        | Serves                                                                                                              |
| ------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `3737`  | `https://ab12cd34ef.agent37.app` (default) | The gateway: [chat](/docs/agents-api/chat) at `/v1/responses`, plus sessions, [files](/docs/agents-api/files), models, health |
| `18789` | `https://ab12cd34ef-18789.agent37.app`     | OpenClaw dashboard, its Control UI (browser)                                                                        |
| `7681`  | `https://ab12cd34ef-7681.agent37.app`      | A shell in the container (browser)                                                                                  |
| `8080`  | `https://ab12cd34ef-8080.agent37.app`      | File browser for the workspace (browser)                                                                            |

Same shape as Hermes: chat is the bare URL plus a path, and the dashboard, terminal, and file browser sit on preview URLs. The dashboard here is OpenClaw's own Control UI, served on `18789` instead of Hermes's `9119`.

### What `agent37-claude-code` serves

| Port   | URL                                        | Serves                                                                                                              |
| ------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `3737` | `https://ab12cd34ef.agent37.app` (default) | The gateway: [chat](/docs/agents-api/chat) at `/v1/responses`, plus sessions, [files](/docs/agents-api/files), models, health |
| `7681` | `https://ab12cd34ef-7681.agent37.app`      | A shell in the container (browser)                                                                                  |
| `8080` | `https://ab12cd34ef-8080.agent37.app`      | File browser for the workspace (browser)                                                                            |

The image is lean, with no browser and no desktop, so there is no dashboard port. The terminal on `7681` is where you run `claude auth login`; see [Host Claude Code](/docs/agents-api/claude-code).

### What `agent37-codex` serves

| Port   | URL                                        | Serves                                                                                                              |
| ------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `3737` | `https://ab12cd34ef.agent37.app` (default) | The gateway: [chat](/docs/agents-api/chat) at `/v1/responses`, plus sessions, [files](/docs/agents-api/files), models, health |
| `7681` | `https://ab12cd34ef-7681.agent37.app`      | A shell in the container (browser)                                                                                  |
| `8080` | `https://ab12cd34ef-8080.agent37.app`      | File browser for the workspace (browser)                                                                            |

The image is lean, with no browser and no desktop, so there is no dashboard port. The terminal on `7681` is where you run `codex login --device-auth`; see [Host Codex](/docs/agents-api/codex).

### What `agent37-grok` serves

| Port   | URL                                        | Serves                                                                                                              |
| ------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `3737` | `https://ab12cd34ef.agent37.app` (default) | The gateway: [chat](/docs/agents-api/chat) at `/v1/responses`, plus sessions, [files](/docs/agents-api/files), models, health |
| `7681` | `https://ab12cd34ef-7681.agent37.app`      | A shell in the container (browser)                                                                                  |
| `8080` | `https://ab12cd34ef-8080.agent37.app`      | File browser for the workspace (browser)                                                                            |

The image is lean, with no browser and no desktop, so there is no dashboard port. Grok reads `XAI_API_KEY` from the instance env, so there is usually nothing to log in to; see [Host Grok](/docs/agents-api/grok).

### What `agent37-opencode` serves

| Port   | URL                                        | Serves                                                                                                              |
| ------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `3737` | `https://ab12cd34ef.agent37.app` (default) | The gateway: [chat](/docs/agents-api/chat) at `/v1/responses`, plus sessions, [files](/docs/agents-api/files), models, health |
| `7681` | `https://ab12cd34ef-7681.agent37.app`      | A shell in the container (browser)                                                                                  |
| `8080` | `https://ab12cd34ef-8080.agent37.app`      | File browser for the workspace (browser)                                                                            |

The image is lean, with no browser and no desktop, so there is no dashboard port. OpenCode runs on the managed model out of the box, so there is nothing to log in to; see [Host OpenCode](/docs/agents-api/opencode).

### What `agent37-n8n` serves

| Port   | URL                                                                                                         | Serves                                                         |
| ------ | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `5678` | `https://ab12cd34ef.agent37.app` (default) and `https://n8n-ab12cd34ef.agent37.app` (public, no credential) | The n8n editor, webhooks (`/webhook/...`), and OAuth callbacks |

This template runs a web app, not an agent: there is no gateway, terminal, or file browser port, and no chat API. Every create mints the public port on `5678` with the fixed prefix `n8n`, so the public URL is the one to open in a browser; n8n's own login guards it. See [Host n8n](/docs/agents-api/n8n).

## Authentication

A port accepts either credential, so you can reach the same port two ways:

* **API key header** (`X-Agent37-Key`) for API calls.
* **Signed URL** for handing a browser tab to a person.

There is a third option for callers that can't send either, webhooks most commonly: give the port its own permanent unauthenticated URL. See [Public ports](/docs/agents-api/public-ports).

### API key header (programmatic)

Every request to an instance URL can carry the same workspace API key as the hosting API, sent raw in the `X-Agent37-Key` header:

```
X-Agent37-Key: sk_live_...
```

The platform edge authenticates the key, checks that the instance belongs to your workspace, and forwards the request to the instance's port. A request without a credential gets `401`; a key from another workspace gets `404`.

Requests are forwarded to your instance with `Authorization` untouched, so software inside the instance can run its own Bearer authentication; the platform never forwards an `sk_live_` key into an instance. Earlier versions authenticated instance URLs with `Authorization: Bearer sk_live_...` instead; that form still works but is deprecated. See [Migrating from `Authorization: Bearer`](#migrating-from-authorization-bearer).

The key travels in a header. To open a preview URL like the dashboard, terminal, or file browser in a browser, mint a signed URL instead.

### Browser access with signed URLs

A signed URL is a time-boxed link a browser can open with no header, usually a preview URL. Mint one for any exposed port on the hosting API:

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.agent37.com/v1/instances/ab12cd34ef/signed-url \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{ "port": 9119 }'
  ```

  ```python python theme={null}
  import requests

  r = requests.post(
      "https://api.agent37.com/v1/instances/ab12cd34ef/signed-url",
      headers={"Authorization": "Bearer sk_live_..."},
      json={"port": 9119},
  )
  print(r.json()["url"])
  ```

  ```javascript node theme={null}
  const res = await fetch("https://api.agent37.com/v1/instances/ab12cd34ef/signed-url", {
    method: "POST",
    headers: { Authorization: "Bearer sk_live_...", "Content-Type": "application/json" },
    body: JSON.stringify({ port: 9119 }),
  });
  console.log((await res.json()).url);
  ```

  ```json response theme={null}
  {
    "url": "https://ab12cd34ef-9119.agent37.app/?a37_token=6a2b...e1f0",
    "domain_urls": [],
    "port": 9119,
    "expires_at": 1717999200
  }
  ```
</CodeGroup>

<ResponseField name="url" type="string">
  The browser-openable URL. The first request promotes the token to a cookie that expires with it, so the page's own assets and WebSocket connections authenticate without it.
</ResponseField>

<ResponseField name="domain_urls" type="string[]">
  The same link on each of the workspace's active [custom domains](/docs/agents-api/domains), oldest first. The token is bound to the label, not the host, so every entry works. Empty without one.
</ResponseField>

<ResponseField name="port" type="integer">
  The port the URL routes to.
</ResponseField>

<ResponseField name="expires_at" type="integer">
  Unix seconds when the URL stops working. Mint a fresh one when it expires.
</ResponseField>

The instance must be running, sleeping, or waking (opening the link is exactly the kind of request that [wakes it](#sleeping-instances-wake-on-request), and a link minted mid-wake works once the wake completes); any `port` from 1 to 65535 mints, except `22022`, the [SSH](/docs/agents-api/ssh) port, which no credential-free URL can ever reach. An optional `ttl_seconds` sets how long the URL lives, default `3600` (one hour), clamped to `[60, 604800]` (one minute to seven days); pass a short value for a quick preview link, or the max to keep a terminal or dashboard tab open for a week. A missing or invalid port, or a `ttl_seconds` that is not a positive integer, returns `400`; an unknown or cross-workspace instance returns `404`.

The token rides in the `a37_token` query param. The edge consumes it (promoting it to the cookie) and strips it before forwarding, so it never reaches the instance and never collides with a query param your own app uses.

For user-facing browser access, mint the signed URL on demand and hand the link to the browser. The link is the only credential it carries, and it expires. A minted link cannot be revoked before it expires, so pick the shortest TTL your flow tolerates: a long-lived tab is worth seven days, a link in an email is not.

### Migrating from `Authorization: Bearer`

Earlier versions authenticated instance URLs with `Authorization: Bearer sk_live_...`. That form still works, but it is deprecated and will eventually stop being accepted; every workspace still sending it gets an email before that happens. It is the same key either way, so there is nothing to rotate, nothing to mint, and nothing to change in the dashboard.

Rename the header on every request that targets an instance URL: the bare instance URL, preview URLs, and any [custom domain](/docs/agents-api/domains) mirror of either.

```diff theme={null}
  curl https://ab12cd34ef.agent37.app/v1/responses \
-   -H "Authorization: Bearer sk_live_..." \
+   -H "X-Agent37-Key: sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{ "input": "Research the top 3 EV makers, write a memo." }'
```

Leave the hosting API as it is. `https://api.agent37.com` keeps `Authorization: Bearer sk_live_...` and always will. One key, two planes: talking to us is `Authorization`, talking to your instance is `X-Agent37-Key`.

This is a straight swap, not a staged rollout: the old form keeps working until it is removed, so rename the header and ship. If a request does carry both, `X-Agent37-Key` is the one consulted, and an invalid value there is a `401` with no fallback to `Authorization`.

The reason for the change is that `Authorization` belongs to your software. With the platform out of that header, an app running inside your instance receives its caller's own `Bearer` untouched, including agent software that does its own token auth, such as the Hermes dashboard on `9119`. The single exception is the platform key itself: an `Authorization` value beginning with `Bearer sk_live_` is stripped at the edge and never enters an instance, on any path.

## Call an instance

Hit the bare URL directly. The gateway's health endpoint is a quick reachability check:

<CodeGroup>
  ```bash curl theme={null}
  curl https://ab12cd34ef.agent37.app/v1/health \
    -H "X-Agent37-Key: sk_live_..."
  ```

  ```python python theme={null}
  import requests

  r = requests.get(
      "https://ab12cd34ef.agent37.app/v1/health",
      headers={"X-Agent37-Key": "sk_live_..."},
  )
  print(r.json())
  ```

  ```javascript node theme={null}
  const res = await fetch("https://ab12cd34ef.agent37.app/v1/health", {
    headers: { "X-Agent37-Key": "sk_live_..." },
  });
  console.log(await res.json());
  ```

  ```json response theme={null}
  { "ok": true, "agent": "hermes", "healthy": true, "hermes": true }
  ```
</CodeGroup>

<Note>
  Use the gateway's `/v1/health` for this, not `/health`: the bare path `/health` is reserved by the platform edge, which answers `{ "ok": true }` itself, without authentication and without reaching your instance.
</Note>

## Sleeping instances wake on request

An [auto-sleep](/docs/agents-api/instances#auto-sleep) instance that has gone to sleep keeps every one of its URLs. A request to any of them wakes it transparently: the edge holds the request while the instance restores, then forwards it. Restoring the checkpoint takes a few seconds. When the wake has to rebuild the instance instead (it was moved to another host, pulled back from cold storage after a long sleep, or the restore failed), it takes about two minutes and boots fresh: processes and in-memory state from before the sleep are gone, files on disk are kept. No status polling, no retry logic; the response you get is the response to the request you sent. No response headers arrive until the wake completes, so give the client a first-byte timeout of at least three minutes.

A wake can be refused or fail, and those answers use the same flat error shape as the other [transport errors](/docs/agents-api/errors#transport-errors):

| Code                 | HTTP | When                                                                                                                                                                 |
| -------------------- | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `instance_suspended` | 402  | The workspace balance is negative and the instance is suspended. No wake is attempted; top up and retry. See [Billing](/docs/agents-api/billing#past-due-and-suspension). |
| `wake_timeout`       | 503  | The wake did not complete within about 3 minutes. The instance keeps waking in the background; retry.                                                                |
| `wake_failed`        | 503  | The wake failed. The instance stays `sleeping` and the next request tries again.                                                                                     |

## HTTP, SSE, and WebSocket

Plain HTTP requests, SSE streams, and WebSocket connections all pass end to end. [Streaming chat](/docs/agents-api/streaming) with `stream: true` works on the bare URL, and the connection stays open until the server closes it after the terminal event. WebSocket upgrades pass through too: that is what the browser terminal uses for its interactive shell.

## Long requests

A request can take as long as it needs. The limit is on silence, not on total time: **15 minutes**, both before the response starts and between bytes once it has. An endpoint that sends nothing at all for that long is answered with `504 upstream_timeout`, and so is a response that stalls that long mid-body. The work itself keeps running inside the instance.

So an install, a migration or a large `git push` behind an instance URL needs no streaming and no job queue: do the work, answer when it is done. For work that can run past 15 minutes, send the response headers first and keep something moving, a heartbeat line or an SSE stream: every byte restarts the clock, so a stream that keeps talking has no ceiling of its own.

One interaction to watch with [auto-sleep](/docs/agents-api/instances#auto-sleep): idle is measured in bytes moved, so an instance whose `idle_timeout_seconds` is shorter than its own slowest call can be checkpointed while that call is still running. Keep the idle timeout at or above the longest request you expect. The default, 900 seconds, matches the 15-minute ceiling.

## Private sandboxes

A [template](/docs/agents-api/templates) whose image listens on nothing still runs fine: with `default_port` omitted, boot is not probed, and every URL simply answers a transport error because nothing serves. Drive the instance from the hosting API with [exec](/docs/agents-api/exec), which runs shell commands inside it without touching any port.
