The node side of sandbox port previews. People and agents do not call it directly: they open `https://usercontent.b3os.org/sbx/<host_id>/<port>/<token>/<rest>`, and the usercontent Worker checks the token, then forwards the request here (through the tunnel, like the edge) with the token in `x-b3os-port-token`. A path that ends at the token is redirected to the same path plus `/`, so relative URLs resolve under it. beta-api.b3os.org and beta.b3os.org never serve this route (`x-b3os-host: usercontent`).
**Port token** (from `POST /v1/sandboxes/{sandbox_id}/ports/{port}/url`, or a public port's `ports[].url`): `pt1.<node>.<exp>.<sig>`. `node` is the signing node's ID (`n1`), `exp` the expiry in Unix seconds (decimal, no leading zeros) and `sig` the base64url (no padding) Ed25519 signature, made with the node key (the one that signs usage batches) over the UTF-8 bytes of
``` "b3os-port-v1
" + node + "
" + sandbox_id + "
" + port + "
" + exp ```
with `sandbox_id` in `sbx_` form (not the host-safe form) and `port` in decimal. A token is good for that sandbox and port only, until `exp`. The Worker checks the signature with the public key it holds for `node` (`verify_strict`) and the expiry before forwarding; the node checks both again, that `node` is its own ID, that the sandbox is running and that the port is declared, then connects to the port inside the sandbox over vsock. A refused token is `403 signed_url_invalid` with `reason`.
**Forwarding.** The proxy forwards exactly seven methods, each declared here as its own operation: `GET` (WebSocket upgrades included), `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` and `OPTIONS` (`sandboxesProxyPortGet` … `sandboxesProxyPortOptions`). Every other method, `TRACE` and `CONNECT` included, is refused with `405 method_not_allowed` and an `Allow` header naming the seven. The app receives the request at `/<rest>`, without the `/sbx/<host_id>/<port>/<token>` prefix; `rest` may be empty (`…/proxy/` reaches the app's root), and it and the query string are passed byte for byte. The request carries `X-Forwarded-Proto: https`, `X-Forwarded-Host: usercontent.b3os.org` and `X-Forwarded-Prefix: /sbx/<host_id>/<port>/<token>`. The response comes back as the app sends it (status, headers, streamed body) minus hop-by-hop headers, `Set-Cookie`, and the two headers that would reach past the preview's own path on the shared origin, `Service-Worker-Allowed` and `Clear-Site-Data`; `Cookie` is removed from requests too (no cookies on usercontent in either direction). Unless the app sets `Referrer-Policy`, the proxy adds `Referrer-Policy: strict-origin`, so the token in the path is not sent to other sites. A response starts within 65 seconds; a WebSocket or a streamed body stays open up to an hour.
**Base path.** All previews share the `usercontent.b3os.org` origin in v0.1 (per-port hostnames are planned): a page served from one port can send same-origin requests to any other usercontent URL it knows, so keep secrets out of previews. Apps should emit relative URLs. Two values describe the base path:
- `X-Forwarded-Prefix` (on every forwarded request) is the request's full base path, `/sbx/<host_id>/<port>/<token>`. Build absolute paths from it. - `B3OS_PORT_BASE_PATH` (in the sandbox's environment) is only the fixed start of that path, `/sbx/<host_id>`, the same for every port and URL of the sandbox. It is not a full base path: the port and the token follow it, and the token differs per minted URL, so no value set when the sandbox starts can hold it.
A public port's token lasts 7 days from sandbox creation, so its URL and base path (`ports[].url`) stay fixed; a sandbox that runs longer gets a new one.
The node side of sandbox port previews. People and agents do not call it directly: they open
`https://usercontent.b3os.org/sbx/<host_id>/<port>/<token>/<rest>`, and the usercontent Worker checks the
token, then forwards the request here (through the tunnel, like the edge) with the token in
`x-b3os-port-token`. A path that ends at the token is redirected to the same path plus `/`, so relative URLs
resolve under it. beta-api.b3os.org and beta.b3os.org never serve this route (`x-b3os-host: usercontent`).
**Port token** (from `POST /v1/sandboxes/{sandbox_id}/ports/{port}/url`, or a public port's `ports[].url`):
`pt1.<node>.<exp>.<sig>`. `node` is the signing node's ID (`n1`), `exp` the expiry in Unix seconds (decimal,
no leading zeros) and `sig` the base64url (no padding) Ed25519 signature, made with the node key (the one that
signs usage batches) over the UTF-8 bytes of
```
"b3os-port-v1\n" + node + "\n" + sandbox_id + "\n" + port + "\n" + exp
```
with `sandbox_id` in `sbx_` form (not the host-safe form) and `port` in decimal. A token is good for that
sandbox and port only, until `exp`. The Worker checks the signature with the public key it holds for `node`
(`verify_strict`) and the expiry before forwarding; the node checks both again, that `node` is its own ID,
that the sandbox is running and that the port is declared, then connects to the port inside the sandbox over
vsock. A refused token is `403 signed_url_invalid` with `reason`.
**Forwarding.** The proxy forwards exactly seven methods, each declared here as its own operation: `GET`
(WebSocket upgrades included), `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` and `OPTIONS`
(`sandboxesProxyPortGet` … `sandboxesProxyPortOptions`). Every other method, `TRACE` and `CONNECT` included,
is refused with `405 method_not_allowed` and an `Allow` header naming the seven. The app receives the
request at `/<rest>`, without the `/sbx/<host_id>/<port>/<token>` prefix; `rest` may be empty (`…/proxy/`
reaches the app's root), and it and the query string are passed byte for byte. The request carries
`X-Forwarded-Proto: https`, `X-Forwarded-Host: usercontent.b3os.org` and
`X-Forwarded-Prefix: /sbx/<host_id>/<port>/<token>`. The response
comes back as the app sends it (status, headers, streamed body) minus hop-by-hop headers, `Set-Cookie`,
and the two headers that would reach past the preview's own path on the shared origin,
`Service-Worker-Allowed` and `Clear-Site-Data`; `Cookie` is removed from requests too (no cookies on
usercontent in either direction). Unless the app sets
`Referrer-Policy`, the proxy adds `Referrer-Policy: strict-origin`, so the token in the path is not sent to
other sites. A response starts within 65 seconds; a WebSocket or a streamed body stays open up to an hour.
**Base path.** All previews share the `usercontent.b3os.org` origin in v0.1 (per-port hostnames are planned):
a page served from one port can send same-origin requests to any other usercontent URL it knows, so keep
secrets out of previews. Apps should emit relative URLs. Two values describe the base path:
- `X-Forwarded-Prefix` (on every forwarded request) is the request's full base path,
`/sbx/<host_id>/<port>/<token>`. Build absolute paths from it.
- `B3OS_PORT_BASE_PATH` (in the sandbox's environment) is only the fixed start of that path, `/sbx/<host_id>`,
the same for every port and URL of the sandbox. It is not a full base path: the port and the token follow
it, and the token differs per minted URL, so no value set when the sandbox starts can hold it.
A public port's token lasts 7 days from sandbox creation, so its URL and base path (`ports[].url`) stay fixed;
a sandbox that runs longer gets a new one.
Path Parameters
sandbox_idstringrequiredpath
The sandbox ID (`sbx_…`).
Example: "sbx_01m5t68q1b590rj801nbzsqzkk"
portintegerrequiredpath
A port declared in the sandbox's `ports`.
Example: 3000
reststringrequiredpath
The rest of the path after the usercontent prefix, slashes included, possibly empty (the app's root). Passed
to the app as `/<rest>` byte for byte.
Example: "assets/app.js"
Header Parameters
x-b3os-port-tokenstringrequiredheader
The port token from the usercontent path (`pt1.<node>.<exp>.<sig>`; see `sandboxesProxyPortGet`), set by the
usercontent Worker. Missing, malformed, expired or not matching this sandbox and port: `403 signed_url_invalid`.
Stable machine-readable error code from the errors catalog.
detailstringREQUIRED
Explanation of this occurrence. Never contains secrets.
instancestring
The request ID (`req_…`) of the request that failed. Include it when you contact support.
errorsobject[]
Validation problems, one per field (`invalid_request` and the other codes that list `errors`).
Array of:
pointerstringREQUIRED
Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`,
`#path/<name>` or `#header/<name>` for parameters.
detailstringREQUIRED
What is wrong there.
reasonstring
Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).
required_scopestring
The scope that was missing (`forbidden` with `reason: scope`).
retry_after_sinteger
Same value as the `Retry-After` header, for clients that only read the body.
limitobject
The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.
idstringREQUIRED
Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.
limitnumberREQUIRED
The limit's value.
unitstringREQUIRED
What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.
window_sinteger
Window length in seconds for rate limits and quotas.
currentnumber
Current usage against the limit, when known.
current_statusstring
The object's current status, for state conflicts such as `sandbox_not_running`.
upstream_statusinteger
The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).
stepstring
The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).
pack_errorobject
The error a pack's error mapping chose (`upstream_error` from pack actions).
codestringREQUIRED
messagestringREQUIRED
retryablebooleanREQUIRED
403The port token is missing, malformed, expired, for another sandbox or port, or not signed by this node (`signed_url_invalid`).
application/problem+json
typestring (uri)REQUIRED
`https://docs.beta.b3os.org/errors#<code>`.
titlestringREQUIRED
Short summary of the problem type.
statusintegerREQUIRED
The HTTP status code, repeated for convenience.
codestringREQUIRED
Stable machine-readable error code from the errors catalog.
detailstringREQUIRED
Explanation of this occurrence. Never contains secrets.
instancestring
The request ID (`req_…`) of the request that failed. Include it when you contact support.
errorsobject[]
Validation problems, one per field (`invalid_request` and the other codes that list `errors`).
Array of:
pointerstringREQUIRED
Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`,
`#path/<name>` or `#header/<name>` for parameters.
detailstringREQUIRED
What is wrong there.
reasonstring
Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).
required_scopestring
The scope that was missing (`forbidden` with `reason: scope`).
retry_after_sinteger
Same value as the `Retry-After` header, for clients that only read the body.
limitobject
The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.
idstringREQUIRED
Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.
limitnumberREQUIRED
The limit's value.
unitstringREQUIRED
What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.
window_sinteger
Window length in seconds for rate limits and quotas.
currentnumber
Current usage against the limit, when known.
current_statusstring
The object's current status, for state conflicts such as `sandbox_not_running`.
upstream_statusinteger
The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).
stepstring
The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).
pack_errorobject
The error a pack's error mapping chose (`upstream_error` from pack actions).
codestringREQUIRED
messagestringREQUIRED
retryablebooleanREQUIRED
404No such sandbox, or the port is not declared in its `ports` (`sandbox_not_found`, `port_not_exposed`).
application/problem+json
typestring (uri)REQUIRED
`https://docs.beta.b3os.org/errors#<code>`.
titlestringREQUIRED
Short summary of the problem type.
statusintegerREQUIRED
The HTTP status code, repeated for convenience.
codestringREQUIRED
Stable machine-readable error code from the errors catalog.
detailstringREQUIRED
Explanation of this occurrence. Never contains secrets.
instancestring
The request ID (`req_…`) of the request that failed. Include it when you contact support.
errorsobject[]
Validation problems, one per field (`invalid_request` and the other codes that list `errors`).
Array of:
pointerstringREQUIRED
Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`,
`#path/<name>` or `#header/<name>` for parameters.
detailstringREQUIRED
What is wrong there.
reasonstring
Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).
required_scopestring
The scope that was missing (`forbidden` with `reason: scope`).
retry_after_sinteger
Same value as the `Retry-After` header, for clients that only read the body.
limitobject
The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.
idstringREQUIRED
Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.
limitnumberREQUIRED
The limit's value.
unitstringREQUIRED
What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.
window_sinteger
Window length in seconds for rate limits and quotas.
currentnumber
Current usage against the limit, when known.
current_statusstring
The object's current status, for state conflicts such as `sandbox_not_running`.
upstream_statusinteger
The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).
stepstring
The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).
pack_errorobject
The error a pack's error mapping chose (`upstream_error` from pack actions).
codestringREQUIRED
messagestringREQUIRED
retryablebooleanREQUIRED
409The sandbox is paused or stopped (`sandbox_not_running`).
application/problem+json
typestring (uri)REQUIRED
`https://docs.beta.b3os.org/errors#<code>`.
titlestringREQUIRED
Short summary of the problem type.
statusintegerREQUIRED
The HTTP status code, repeated for convenience.
codestringREQUIRED
Stable machine-readable error code from the errors catalog.
detailstringREQUIRED
Explanation of this occurrence. Never contains secrets.
instancestring
The request ID (`req_…`) of the request that failed. Include it when you contact support.
errorsobject[]
Validation problems, one per field (`invalid_request` and the other codes that list `errors`).
Array of:
pointerstringREQUIRED
Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`,
`#path/<name>` or `#header/<name>` for parameters.
detailstringREQUIRED
What is wrong there.
reasonstring
Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).
required_scopestring
The scope that was missing (`forbidden` with `reason: scope`).
retry_after_sinteger
Same value as the `Retry-After` header, for clients that only read the body.
limitobject
The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.
idstringREQUIRED
Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.
limitnumberREQUIRED
The limit's value.
unitstringREQUIRED
What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.
window_sinteger
Window length in seconds for rate limits and quotas.
currentnumber
Current usage against the limit, when known.
current_statusstring
The object's current status, for state conflicts such as `sandbox_not_running`.
upstream_statusinteger
The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).
stepstring
The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).
pack_errorobject
The error a pack's error mapping chose (`upstream_error` from pack actions).
codestringREQUIRED
messagestringREQUIRED
retryablebooleanREQUIRED
429A rate, concurrency or quota limit applies (`rate_limited`, `concurrency_limited`, `quota_exceeded`).
application/problem+json
typestring (uri)REQUIRED
`https://docs.beta.b3os.org/errors#<code>`.
titlestringREQUIRED
Short summary of the problem type.
statusintegerREQUIRED
The HTTP status code, repeated for convenience.
codestringREQUIRED
Stable machine-readable error code from the errors catalog.
detailstringREQUIRED
Explanation of this occurrence. Never contains secrets.
instancestring
The request ID (`req_…`) of the request that failed. Include it when you contact support.
errorsobject[]
Validation problems, one per field (`invalid_request` and the other codes that list `errors`).
Array of:
pointerstringREQUIRED
Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`,
`#path/<name>` or `#header/<name>` for parameters.
detailstringREQUIRED
What is wrong there.
reasonstring
Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).
required_scopestring
The scope that was missing (`forbidden` with `reason: scope`).
retry_after_sinteger
Same value as the `Retry-After` header, for clients that only read the body.
limitobject
The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.
idstringREQUIRED
Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.
limitnumberREQUIRED
The limit's value.
unitstringREQUIRED
What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.
window_sinteger
Window length in seconds for rate limits and quotas.
currentnumber
Current usage against the limit, when known.
current_statusstring
The object's current status, for state conflicts such as `sandbox_not_running`.
upstream_statusinteger
The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).
stepstring
The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).
pack_errorobject
The error a pack's error mapping chose (`upstream_error` from pack actions).
codestringREQUIRED
messagestringREQUIRED
retryablebooleanREQUIRED
500Something failed inside B3OS (`internal`). Retry with backoff.
application/problem+json
typestring (uri)REQUIRED
`https://docs.beta.b3os.org/errors#<code>`.
titlestringREQUIRED
Short summary of the problem type.
statusintegerREQUIRED
The HTTP status code, repeated for convenience.
codestringREQUIRED
Stable machine-readable error code from the errors catalog.
detailstringREQUIRED
Explanation of this occurrence. Never contains secrets.
instancestring
The request ID (`req_…`) of the request that failed. Include it when you contact support.
errorsobject[]
Validation problems, one per field (`invalid_request` and the other codes that list `errors`).
Array of:
pointerstringREQUIRED
Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`,
`#path/<name>` or `#header/<name>` for parameters.
detailstringREQUIRED
What is wrong there.
reasonstring
Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).
required_scopestring
The scope that was missing (`forbidden` with `reason: scope`).
retry_after_sinteger
Same value as the `Retry-After` header, for clients that only read the body.
limitobject
The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.
idstringREQUIRED
Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.
limitnumberREQUIRED
The limit's value.
unitstringREQUIRED
What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.
window_sinteger
Window length in seconds for rate limits and quotas.
currentnumber
Current usage against the limit, when known.
current_statusstring
The object's current status, for state conflicts such as `sandbox_not_running`.
upstream_statusinteger
The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).
stepstring
The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).
pack_errorobject
The error a pack's error mapping chose (`upstream_error` from pack actions).
codestringREQUIRED
messagestringREQUIRED
retryablebooleanREQUIRED
502Nothing in the sandbox accepted a connection on the port (`upstream_unreachable`), or the guest agent did not answer (`guest_unreachable`).
application/problem+json
typestring (uri)REQUIRED
`https://docs.beta.b3os.org/errors#<code>`.
titlestringREQUIRED
Short summary of the problem type.
statusintegerREQUIRED
The HTTP status code, repeated for convenience.
codestringREQUIRED
Stable machine-readable error code from the errors catalog.
detailstringREQUIRED
Explanation of this occurrence. Never contains secrets.
instancestring
The request ID (`req_…`) of the request that failed. Include it when you contact support.
errorsobject[]
Validation problems, one per field (`invalid_request` and the other codes that list `errors`).
Array of:
pointerstringREQUIRED
Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`,
`#path/<name>` or `#header/<name>` for parameters.
detailstringREQUIRED
What is wrong there.
reasonstring
Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).
required_scopestring
The scope that was missing (`forbidden` with `reason: scope`).
retry_after_sinteger
Same value as the `Retry-After` header, for clients that only read the body.
limitobject
The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.
idstringREQUIRED
Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.
limitnumberREQUIRED
The limit's value.
unitstringREQUIRED
What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.
window_sinteger
Window length in seconds for rate limits and quotas.
currentnumber
Current usage against the limit, when known.
current_statusstring
The object's current status, for state conflicts such as `sandbox_not_running`.
upstream_statusinteger
The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).
stepstring
The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).
pack_errorobject
The error a pack's error mapping chose (`upstream_error` from pack actions).
codestringREQUIRED
messagestringREQUIRED
retryablebooleanREQUIRED
503The node or the product is not available right now (`node_unavailable`, `product_unavailable`, `capacity_unavailable`).
application/problem+json
typestring (uri)REQUIRED
`https://docs.beta.b3os.org/errors#<code>`.
titlestringREQUIRED
Short summary of the problem type.
statusintegerREQUIRED
The HTTP status code, repeated for convenience.
codestringREQUIRED
Stable machine-readable error code from the errors catalog.
detailstringREQUIRED
Explanation of this occurrence. Never contains secrets.
instancestring
The request ID (`req_…`) of the request that failed. Include it when you contact support.
errorsobject[]
Validation problems, one per field (`invalid_request` and the other codes that list `errors`).
Array of:
pointerstringREQUIRED
Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`,
`#path/<name>` or `#header/<name>` for parameters.
detailstringREQUIRED
What is wrong there.
reasonstring
Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).
required_scopestring
The scope that was missing (`forbidden` with `reason: scope`).
retry_after_sinteger
Same value as the `Retry-After` header, for clients that only read the body.
limitobject
The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.
idstringREQUIRED
Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.
limitnumberREQUIRED
The limit's value.
unitstringREQUIRED
What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.
window_sinteger
Window length in seconds for rate limits and quotas.
currentnumber
Current usage against the limit, when known.
current_statusstring
The object's current status, for state conflicts such as `sandbox_not_running`.
upstream_statusinteger
The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).
stepstring
The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).
pack_errorobject
The error a pack's error mapping chose (`upstream_error` from pack actions).
{ "type": "https://docs.beta.b3os.org/errors#invalid_request", "title": "Invalid request", "status": 400, "code": "invalid_request", "detail": "1 field failed validation", "instance": "req_01m5t4yg394wegr3j4y01qb363", "errors": [ { "pointer": "/url", "detail": "must be an absolute http or https URL" } ]}
403Response
{ "type": "https://docs.beta.b3os.org/errors#signed_url_invalid", "title": "Invalid or expired signed URL", "status": 403, "code": "signed_url_invalid", "detail": "The preview URL expired at 2026-10-25T19:11:00Z; request a new one with POST /v1/sandboxes/sbx_01m5t68q1b590rj801nbzsqzkk/ports/3000/url", "instance": "req_01m5t4yg394wegr3j4y01qb363", "reason": "expired"}
404Response
{ "type": "https://docs.beta.b3os.org/errors#port_not_exposed", "title": "Port not exposed", "status": 404, "code": "port_not_exposed", "detail": "Port 8080 is not in the ports of sbx_01m5t68q1b590rj801nbzsqzkk", "instance": "req_01m5t4yg394wegr3j4y01qb363"}
409Response
{ "type": "https://docs.beta.b3os.org/errors#sandbox_not_running", "title": "Sandbox not running", "status": 409, "code": "sandbox_not_running", "detail": "sbx_01m5t68q1b590rj801nbzsqzkk is paused; resume it first", "instance": "req_01m5t4yg394wegr3j4y01qb363", "current_status": "paused"}