Unavailable Responses

What visitors to your app see when Volcano stops serving your project's traffic, and how to detect it from a client.

When Volcano stops serving traffic for a project, every data-plane path answers with the same status and copy: HTTP 503 Service Unavailable, and no caching. Frontends, function invocations, storage, the database REST API, realtime, and pgproxy all behave identically.

The response is written for your app's end users, not for you. It never mentions your account, plan, billing state, or project — a visitor cannot tell why traffic stopped. The branded HTML page does link back to Volcano's status page, but withholds every detail about your account.

Detecting it from a client

API, function, and storage callers get JSON:

{
  "error": "This site is temporarily unavailable.",
  "code": "service_unavailable"
}

Match on code, not on the human-readable text:

const res = await fetch('https://your-site.frontends.volcano.run/api/items');

if (res.status === 503) {
  const body = await res.json().catch(() => null);
  if (body?.code === 'service_unavailable') {
    // Traffic is paused. Back off and retry.
  }
}

Every unavailable response carries these headers:

HeaderValueMeaning
Cache-Controlno-storeNever cached, so service resumes immediately once traffic is restored
Retry-After60Suggested retry delay, in seconds
X-Robots-TagnoindexKeeps the page out of search results

Cross-origin callers

The unavailable response always carries CORS headers, so an in-page fetch() can read the JSON body instead of failing with an opaque cross-origin error. The request Origin is echoed (with credentials allowed) when one is present, and * is used otherwise.

These headers are set on the unavailable response itself rather than resolved from your project's CORS configuration, so a request from an origin your config does not normally allow can still read this particular 503. The body contains no information about your account, so there is nothing to disclose. A response that already carries CORS headers — because a path resolved your project's real policy first — keeps those headers; this only fills the gap for paths that never resolve a policy at all. Your CORS policy continues to apply to every other response.

Browser navigations

A top-level browser navigation receives a self-contained branded HTML page instead of JSON. This is standard HTTP content negotiation on the Accept header: a browser's default navigation Accept prefers text/html, while fetch()/XMLHttpRequest and non-browser clients default to */* or application/json and get the JSON body.

If your own page explicitly sets Accept: text/html on an in-page fetch() call, it will receive the HTML page too — pass an explicit Accept: application/json on any request whose response your code parses as JSON.

Per-protocol behavior

SurfaceResponse
FrontendsHTML page for navigations, JSON otherwise
Function invocation503 with the JSON body, regardless of Accept
Storage503 with the JSON body, regardless of Accept
Database REST API503 with the JSON body, regardless of Accept
Auth-user endpoints (signup, signin, refresh, profile, etc.)503 with the JSON body, regardless of Accept
RealtimeWebSocket close code 4503
pgproxyPostgres ErrorResponse, SQLSTATE 57P03 (cannot_connect_now)

The codes above are what a new connection attempt gets. When read_only status propagates, Volcano also force-closes already-established pgproxy connections and realtime clients, but that teardown does not send these codes: pgproxy drops the socket without an ErrorResponse, and realtime closes with generic close code 3004, not 4503. Treat an unexpected disconnect on a working connection as a signal to reconnect, then read the refusal from the codes above on that next attempt. Neither realtime nor pgproxy re-checks project traffic status on an already-established connection, so a session that was still establishing when status changed can escape the teardown sweep entirely: it keeps working, with no re-check on its later requests, until the client or server ends that connection for an unrelated reason — at which point the next reconnect is refused. This can take time across instances.

Function invocation, storage, the database REST API, and auth-user endpoints never turn a browser-issued Accept header into HTML: a script or SDK calling these must always be able to parse the response as JSON.

Postgres drivers treat 57P03 as a retryable "server not accepting connections" condition, so existing reconnect logic works without changes:

ERROR:  This site is temporarily unavailable.
SQLSTATE: 57P03

Recovery

Traffic resumes automatically once the underlying condition clears — nothing needs to be purged or redeployed. For an account restricted by overage arrears, Volcano requests read_only status until the monthly credit grant or purchased credits clear the arrears, then requests active status. The allowance resetting by itself does not resume usage while arrears remain. The no-store response is never cached, but the allow/deny decision at the traffic gate is cached and refreshes eventually after the status change. A request made immediately after recovery can still receive an unavailable response.

If you see this on your own project, check your account and project status in the Volcano dashboard. The response deliberately withholds the reason from your visitors. See plans and limits for the limits and billing behavior that apply to your project.

Requests from embargoed countries

Volcano does not serve requests from Cuba, Iran, or North Korea, which are under comprehensive US sanctions. Frontends, function invocations, realtime, and the API answer them with 451 Unavailable For Legal Reasons, whatever your project's status:

{
  "error": "Not available in your region.",
  "code": "region_unavailable"
}

Volcano locates the caller by IP address. For an anonymous-auth request from an authenticated server-side auth proxy, that is the browser IP the proxy supplies. Frontends serve a branded HTML page to browser navigations and this JSON otherwise; every other surface answers with JSON. The response is never cached and carries the same CORS headers as the unavailable response, so an in-page fetch() can read it. A CORS preflight from such a caller succeeds, so the request that follows it receives this 451 rather than a CORS error. Realtime callers receive the 451 in place of the WebSocket upgrade.

Postgres connections are refused during startup, before authentication, with SQLSTATE 28000 (invalid_authorization_specification). Unlike 57P03, drivers do not retry it:

ERROR:  Not available in your region.
SQLSTATE: 28000

Temporarily unavailable Sandbox regions

A new Sandbox session or one-shot execution in a temporarily unavailable region answers 400 with code sandbox_region_unavailable. This is not the region_unavailable embargo response: choose another region from GET /sandboxes/presets and retry. See Temporarily unavailable regions.

On this page