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

# App addresses

Each addressed port of an application gets one address on b3os.io that never changes, shared by link or public, with cookies of its own.

An application you deploy with `b3os apply` gets one address per addressed port: `https://<label>.b3os.io`. The commands on this page come with CLI 0.2.1, not published yet.

Addresses are being turned on per organization. Until yours has them, `b3os apply` prints preview URLs as before, and the address routes of the API answer [`404 feature_not_enabled`](/errors#feature_not_enabled). When B3OS cannot read whether yours has them, they answer [`503 flags_unavailable`](/errors#flags_unavailable): try again.

## What you get

- `b3os apply` ends with the address, such as `https://gpt-researcher.b3os.io`. It never changes: not on a push, a rollback, a restart after a crash, or with time.
- Your app is served at `/` on its own origin. No path prefix.
- A push switches the address to the new generation once that generation passes its readiness probe. If the switch fails, the deploy is undone and the old generation keeps serving. When two deploys race, the newer one gets the address, and its `access` and `cookies` stand: an older deploy still running that would set them again stops as stale (`app_host_route_conflict`, reason `newer_deploy`), and so does every deploy that began before you changed them outside a deploy. Every change to an address that serves (a switch, a rotate, a block, `b3os app down`) reaches its visitors within 6 s; longer while B3OS cannot read its address table.
- When nothing serves it, the address answers a page of its own: [`503 app_deploying`](/errors#app_deploying) until the first deploy switches it (the page says `<label> is not live yet` and loads the app by itself when the deploy finishes), [`503 app_not_running`](/errors#app_not_running) after `b3os app down` or while the service is down, and [`410 app_blocked`](/errors#app_blocked) if B3 takes it offline. An unknown label is [`404 app_not_found`](/errors#app_not_found). An address that finds its service down answers `503` for the next 2 s without asking again, so the first good answer after it comes back can wait that long.

## Share by link, or make it public

An address is shared by link unless you say otherwise.

- `b3os app link` prints a link, and the console's Copy link copies one. A link works 7 days; `--ttl` sets another lifetime, up to 90 days. Share it like a password.
- The first visit with a link sets a cookie, and that browser stays in for 30 days. Neither reaches your app: B3OS takes the link out of the URL (the browser is sent on to the same page without it) and out of the `x-b3os-link` header, and the cookie out of the request, before it goes on.
- A visit without a valid link gets [`401 app_link_required`](/errors#app_link_required), the same for a missing and a wrong link.
- A script or another non-browser client sends the link's token, the part after `b3os_link=`, in an `x-b3os-link` header.
- A page on another origin that calls a link address from the browser must put `?b3os_link=<token>` in the URL: the browser's preflight (`OPTIONS`) carries no header and no cookie, and without the link in the URL it gets `401`. Calls from the app's own pages never preflight.
- An image or script of a link address on another site's page arrives without the browser's cookies, so its URL needs `?b3os_link=<token>` too. A public address needs none.
- A link address never serves a sandbox whose port is also public: that port's preview URL would reach the app without a link. A public address turns link only on a sandbox whose port is private ([`422 app_host_port_public`](/errors#app_host_port_public)).
- `b3os app link --rotate`, or Rotate link in the console: every link stops working within 6 s, and so does every browser a link let in; streams already open can run up to an hour.

`access: public` opens the address to anyone on the internet. Its B3OS calls spend from your project, so a public address needs `grant.spend_cap_usd` on its service, and the plan stops without one. In the beta, public addresses are open only to organizations B3 has allowed (`apps.hosts.public_max`, 0 by default; [limits](/concepts/limits#beta-defaults)).

## In b3os.yaml

Four keys on a service's port. A port has an address when it sets `access`, or `public: true`, which means `access: link`. A port cannot be 53, 1080, 3128, 7700 or 15001: B3OS's agent in the sandbox uses them. An application deployed before addresses keeps its file: its next apply gives each `public: true` port its address, and the port's preview URL ends with the old generation. When nothing else in the service changed, the plan says why it replaces the generation: `port 8000 now has the address https://gpt-researcher.b3os.io; its preview URL ends with <old sandbox>; people need a link (b3os app link)`. Otherwise it shows the address line below and the service's own changes.

| Key | Values | Default |
|---|---|---|
| `access` | `link` or `public` | `link` |
| `host` | the label to ask for, read when the port is first claimed | the application's name ([names](#names)) |
| `primary` | `true` on the port that gets the application's own label; needed when more than one port has an address and any port sets `access`, `host`, `primary` or `cookies`. With only `public: true` ports, the first in the file gets the label and the plan says which. | `false` |
| `cookies` | `isolated`, `shared`, or `{ mode, script: [names] }` ([cookies](#cookies)) | `isolated` |

```yaml b3os.yaml
version: 1
name: shop
services:
  - name: web
    command: node server.js
    ports:
      - port: 8000
        access: link
        primary: true
        cookies: { script: [theme] }
  - name: api
    command: [python3, api.py]
    grant: { spend_cap_usd: 5 }
    ports:
      - port: 9000
        access: public
```

`web` gets `https://shop.b3os.io`, shared by link, and `api` gets `https://shop--api.b3os.io`, public. The plan says each address before it is claimed, and once it is yours:

```text
+ address https://shop.b3os.io (free now)
= address https://shop.b3os.io
```

and under a public one, `anyone on the internet can use this service; its B3OS calls spend from this project, up to $5`. `b3os apply --dry-run` claims nothing: "free now" means another organization may still claim the label first. The plan stops on these, with these words:

| The plan says | Fix |
|---|---|
| `port 8000: host, primary and cookies need an address (set access, or public: true)` | give the port `access` |
| `2 ports have an address and none is primary: mark one with primary: true` | mark one port `primary: true` (not asked of a file whose addressed ports are all plain `public: true`) |
| `more than one port is primary` | keep one |
| `port 9000: access public needs grant.spend_cap_usd on this service` | set the service's spend cap |
| `port 9000: access public is not open to this organization yet (apps.hosts.public_max)` | ask B3, or use `access: link` |
| `port 8000: host api: api is reserved` | ask for another `host` ([names](#names)) |
| `port 8000: support-bot: has the word support, which no address may hold; set host on the port` | set `host`: the application's name holds a word no address may hold ([names](#names)) |
| `21 addresses are over your limit of 20 (apps.hosts.max): labels are kept for good (b3os app down frees none), and B3 raises the limit on request` | ask B3 to raise it, or deploy under an application that already has an address |
| `no address is free for shop: set host on port 8000` | set `host` |
| `port 8000: shop.b3os.io: B3OS has taken this address offline. Visitors get 410; contact support` | ask B3: while one of an application's addresses is blocked, no apply of it goes through |

A change to `access` or `cookies` takes effect when the next apply switches the address, not before: visitors of the old generation keep the old rules until then. With no new generation it takes effect as that apply finishes. An address turns public only on a generation that has a spend cap. `host` and `primary` are read only when a port is first claimed: a label never moves to another port, and a port never changes its label.

## Cookies

Cookies are isolated by default because, until b3os.io is on the Public Suffix List, browsers treat every app on b3os.io as one site. A page on another app could set a cookie for all of b3os.io, and your server would take it for its own. So B3OS stores each cookie your server sets as `__Host-b3c.<name>`, which only your own address can set, and gives your server back only those, under their own names. A cookie without `HttpOnly` is also set under its own name, so your page scripts read it as before.

A cookie your page script writes (`document.cookie`) never passed through B3OS, so it is dropped on its way to your server unless you list its name under `cookies.script`. `b3os app status` and the console name each cookie this address did not set and dropped in the last 7 days, with how many requests it was dropped on. A name there may be one another b3os.io app's page set for all of b3os.io, so list a name only if your own page script sets it.

What breaks under isolation:

- A cookie set with a `Path` other than `/` loses its path: it is sent on every path.
- Two cookies of one name on different paths become one.

A cookie your server sets `SameSite=Strict` is stored as `__Host-b3s.<name>`, and B3OS does not pass it on a request that comes from another app on b3os.io, also when that is a link the visitor follows (a browser would not send a Strict cookie across sites).

`cookies: shared` turns isolation off for a port: `Set-Cookie` passes with its `Domain` removed, and your server gets the cookies the browser sends, those other apps' pages set for all of b3os.io included. Under `shared` no cookie is stored under a second name, so a Strict cookie does reach your server from another app's link. In either mode Cloudflare's own cookies never pass, in either direction: `CF_Authorization`, `__cf_bm` and four more, and any name that starts with `cf_`, `__cf` or `_cf`. A cookie listed under `script`, and under `shared` every cookie, your server's own included, reaches your server only with a plain value: one with a space, comma, backslash, quote or control character inside it is dropped. Encode such a value (`encodeURIComponent` does).

## What your app sees

- `Host` and `X-Forwarded-Host` are the address (`shop.b3os.io`), `X-Forwarded-Proto` is `https`, and there is no `X-Forwarded-Prefix`.
- `X-Forwarded-For` is the client's address.
- `B3OS_APP_URL` in the service's environment is the address of its first addressed port, the primary first: `https://shop.b3os.io` for `web`, `https://shop--api.b3os.io` for `api`. Use it for an allowed-hosts list, a dev server's host check or an OAuth callback.

On the way out, a `Clear-Site-Data` header loses `"cookies"` (it would clear every app on b3os.io), and B3OS adds `Strict-Transport-Security` and `Origin-Agent-Cluster: ?1` when your app sends neither. B3OS also adds `Content-Security-Policy: frame-ancestors 'self'` with the application's own addresses to an HTML answer that has neither a `frame-ancestors` policy nor `X-Frame-Options`, so no other site and no other app on b3os.io can frame it. To be embedded, send your own `frame-ancestors` or `X-Frame-Options`: B3OS then leaves framing to you, and another app on b3os.io can frame yours with your visitors' cookies, since browsers count all of b3os.io as one site until it is on the Public Suffix List. A link address also gets `X-Robots-Tag: noindex`.

## Limits

- A request body is at most 64 MiB ([`413 payload_too_large`](/errors#payload_too_large)).
- A request or a stream lasts at most an hour.
- A stream that sends nothing for 100 s is cut: send a heartbeat (an SSE comment, a WebSocket ping).
- A response must start within the port's `first_byte_s`, 65 s unless it declares more ([slow responses](/sandboxes/ports#slow-responses)).
- One client gets at most 600 requests a minute across b3os.io, counted at each Cloudflare location (an IPv6 client counts as its whole /64), then [`429 rate_limited`](/errors#rate_limited).
- A sandbox serves at most 256 requests at once through its addresses, 64 of them from one client address (an IPv6 client by its /64). A request that finds no place waits up to 2 s for one, then gets [`429 concurrency_limited`](/errors#concurrency_limited) with `Retry-After: 1`.
- No gRPC.
- When a push switches the address, the old generation stops seconds later: its open WebSocket and SSE connections close, and clients reconnect to the new one.

## Names

- A label is 1 to 63 lowercase letters, digits and hyphens, with no hyphen at either end. A longer one is cut and ends in 6 hex digits.
- The default is the application's name. If that is taken or is a reserved word, `<name>-<org slug>`, then `<name>-<6 hex digits of your organization's ID>`. A name that holds a word no address may hold (below) gets no default address, since every default holds the name: set `host` on the port. One project gets these: the one the application's GitHub repository link names, else the first to deploy it. Its other projects (environments are projects in the beta) get `<name>-<6 hex digits of the project's ID>`.
- `--` is B3OS's. A further addressed port gets `<label>--<service>` (`<label>--<service>-<port>` when its service has several), which only the label's owner can get. A label of 2 characters gets none (its `--` would stand at characters 3 and 4, which browsers read as an encoded international name), and neither does one of more than 55: set `host` on each further port. `host` cannot hold `--`, and an application name with `--` gets `-` in its label.
- First come: the first `b3os apply` to claim a label gets it, and the same letters without hyphens count as the same label (`gptresearcher` beside `gpt-researcher`), as does a digit for the letter it looks like, or `i` for `l` (`acme-sh0p` beside `acme-shop`, `sh1p` beside `ship`).
- Reserved: words B3OS keeps (`api`, `app`, `www`, `docs`, `status`, `demo`, `test`, `xn` and more), anything that names B3OS (`b3os`, `b3iq`, or `b3` between hyphens), and a word phishing uses (`login`, `pay`, `support`, `admin`, `wallet` and more), which no address may hold, B3's own included. A hyphen does not get around them, and neither do digits for letters: `ad-min` reads as `admin`, `log-in` and `l0gin` as `login`, `supp0rt` as `support`, `b-3os` and `b30s` as `b3os`. [`app_host_label_reserved`](/errors#app_host_label_reserved) says which.
- An organization holds at most 20 addresses, stopped ones included (`apps.hosts.max`; [limits](/concepts/limits#beta-defaults)).
- A label stays yours. `b3os app down` stops it and keeps it, and no other organization ever gets it.

## Not there yet

- A link address inside another site's iframe: the frame's load carries no link, so it gets `401`.
- A page on one link address calling a second link address of the same application. Serve both from one port, or make the API `public` with its own auth.

## Shared-domain limits

B3OS acts as the boundary between apps: a request another app's page sends carries no cookies (a plain page load still does), and the cookie rules above keep each app's server to its own. In Safari before 16.4 an app that sends `Referrer-Policy: no-referrer` gets no cookies on its own requests that are not page loads (images, scripts, fetch GETs), because B3OS cannot tell them from another app's; use `same-origin` or the default policy. The same holds for a client that is no browser but sends a browser's User-Agent with cookies: send the link in `x-b3os-link`, or an `Origin` of the app. Two gaps stay open until b3os.io is on the Public Suffix List and browsers have shipped it. A password manager may offer a password saved on one app on any other app's page. And a page script can set a cookie for all of b3os.io that other apps' page scripts can read; their servers do not get it while they stay `isolated`. Keep secrets out of cookies your scripts can read, and prefer a sign-in provider to a password form of your own.