POST /v1/apps/{app}/hosts/route
Points each label in targets at a sandbox (sandbox_id: null stops the address), all or none: each label's current sandbox must be its if_current entry, which every target needs (app_host_route_conflict otherwise, and nothing changes). Each sandbox must be in your project, running, and declare the port; a public address needs a sandbox whose grant sets spend_cap_usd, and a link address one whose port is private. b3os apply calls this once the new generation is ready, then waits 7 seconds before it stops the old one. Off by default: the platform.app_hosts flag must be on for your organization, or your organization must hold a serving address; without either this answers 404 feature_not_enabled (and b3os apply keeps each port's preview URL). 503 flags_unavailable when that cannot be read.

Authentication

Bearer Token (b3os_sk_ API key or b3os_pat_ token)

Path Parameters

app string required path
The application's name (name in its b3os.yaml).
Min length 1Max length 63Pattern ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$
Example: "research-agent"

Request Body required

application/json
targets object[] REQUIRED
Min items 1Max items 32
Array of:
label string REQUIRED
An address's label, the part before .b3os.io.
Min length 1Max length 63Pattern ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$
sandbox_id string | null REQUIRED
The sandbox to serve, or null to stop the address.
Pattern ^sbx_[0-9a-hjkmnp-tv-z]{26}$
port integer REQUIRED
Min 1Max 65535
if_current object REQUIRED
Per target label, the sandbox it serves now as you read it (null when it serves none).
deploy_id string
Max length 64

Responses

200 The application's addresses after the switch.
application/json
object string REQUIRED
Values list
data object[] REQUIRED
Array of:
object string REQUIRED
Values app_host
label string REQUIRED
An address's label, the part before .b3os.io.
url string (uri) REQUIRED
https://<label>.b3os.io.
app string REQUIRED
An application's name, the name in its b3os.yaml.
environment string REQUIRED
The environment (production; environments are projects in this beta).
project_id string REQUIRED
service string REQUIRED
The service of the application whose port this is.
port integer REQUIRED
access string REQUIRED
link: people who hold a link from b3os app link. public: anyone on the internet.
Values link public
cookies object REQUIRED
mode string REQUIRED
isolated: the app's server gets only the cookies its own address set, plus the names in script. shared: the browser's cookies pass, except Cloudflare's and B3OS's own names and values that are not plain (a space, comma, quote or backslash).
Values isolated shared
script string[] REQUIRED
Cookies a page script writes and the app's server reads, by name.
state string REQUIRED
claimed: the label is the port's and no deploy has switched it yet. serving: it serves sandbox_id. stopped: it serves nothing (b3os app down, or the organization or project stopped). blocked: B3 took it down; it can be neither claimed nor routed.
Values claimed serving stopped blocked
sandbox_id string | null REQUIRED
The sandbox the address serves (serving only).
deploy_id string | null REQUIRED
The deploy that last switched the address.
base_label string | null REQUIRED
The application's own label this one is derived from (<base>--<service>), or null.
primary boolean
The address of the application's primary port, which holds its own label (the others derive from it). Set when that port is first claimed; a later primary in b3os.yaml does not move it. Every answer of the edge has it.
blocked_reason string | null
Why B3 blocked the address (blocked only; else null). Every answer of the edge has it.
claimed_at string (date-time) REQUIRED
updated_at string (date-time) REQUIRED
dropped_cookies object[] REQUIRED
Cookies a page script set that the address did not pass to the app in the last 7 days (cookies: isolated), by name, most requests first. List one under cookies.script to let it through.
Array of:
name string REQUIRED
requests integer REQUIRED
next_cursor string | null REQUIRED
400 The request failed validation (invalid_request, invalid_url).
401 Missing, malformed, expired or revoked credentials (unauthorized).
403 B3 blocked the address; it can be neither claimed nor routed (app_host_blocked).
404 A label is not one of the application's in your project (app_host_not_found), a sandbox is not in your project (sandbox_not_found), or it does not declare the port (port_not_exposed); or addresses are not on for your organization (feature_not_enabled: the platform.app_hosts flag).
409 A label's current sandbox is not its if_current entry, and nothing changed (app_host_route_conflict); or a sandbox is not running (sandbox_not_running).
413 The request body exceeds this operation's limit.
415 The Content-Type is not accepted by this operation.
422 A public address and a sandbox whose grant has no spend_cap_usd (app_host_public_needs_spend_cap), or a link address and a sandbox whose port is public (app_host_port_public). Nothing changed.
429 A rate, concurrency or quota limit applies (rate_limited, concurrency_limited, quota_exceeded).
500 Something failed inside B3OS (internal). Retry with backoff.
503 B3OS could not read whether addresses are on for your organization (flags_unavailable: the flag platform.app_hosts, or whether your organization holds a serving address). Nothing was read or changed; retry after Retry-After seconds. A caller must not read this as "no addresses".
curl -X POST 'https://beta-api.b3os.org/v1/apps/research-agent/hosts/route' \  -H 'Authorization: Bearer YOUR_API_TOKEN' \  -H 'Content-Type: application/json' \  -d '{  "targets": [    {      "label": "gpt-researcher",      "sandbox_id": "sbx_01m5t68q1b590rj801nbzsqzkk",      "port": 8000    }  ],  "if_current": {    "gpt-researcher": null  },  "deploy_id": "dep_01m6a2zq8x9hkqd3n7c5b4v2tw"}'
const response = await fetch('https://beta-api.b3os.org/v1/apps/research-agent/hosts/route', {  method: 'POST',  headers: {      "Authorization": "Bearer YOUR_API_TOKEN",      "Content-Type": "application/json"  },  body: JSON.stringify({    "targets": [      {        "label": "gpt-researcher",        "sandbox_id": "sbx_01m5t68q1b590rj801nbzsqzkk",        "port": 8000      }    ],    "if_current": {      "gpt-researcher": null    },    "deploy_id": "dep_01m6a2zq8x9hkqd3n7c5b4v2tw"  })});const data = await response.json();console.log(data);
import requestsheaders = {    'Authorization': 'Bearer YOUR_API_TOKEN'}response = requests.post('https://beta-api.b3os.org/v1/apps/research-agent/hosts/route', headers=headers, json={  "targets": [    {      "label": "gpt-researcher",      "sandbox_id": "sbx_01m5t68q1b590rj801nbzsqzkk",      "port": 8000    }  ],  "if_current": {    "gpt-researcher": null  },  "deploy_id": "dep_01m6a2zq8x9hkqd3n7c5b4v2tw"})print(response.json())
200 Response
{  "object": "list",  "data": [    {      "object": "app_host",      "label": "gpt-researcher",      "url": "https://gpt-researcher.b3os.io",      "app": "gpt-researcher",      "environment": "production",      "project_id": "prj_01m5t3pf8heewxt65ej3dm577y",      "service": "web",      "port": 8000,      "access": "link",      "cookies": {        "mode": "isolated",        "script": []      },      "state": "serving",      "sandbox_id": "sbx_01m5t68q1b590rj801nbzsqzkk",      "deploy_id": "dep_01m6a2zq8x9hkqd3n7c5b4v2tw",      "base_label": null,      "primary": true,      "blocked_reason": null,      "claimed_at": "2026-11-11T09:00:00.000Z",      "updated_at": "2026-11-11T09:02:00.000Z",      "dropped_cookies": []    }  ],  "next_cursor": null}
Ask a question... ⌘I