API Reference

Routes

Inventory and dashboard

Dashboard → Routes lists all registered public hostnames for the account, with resource ownership, status, namespace and per-hostname analytics. Open a hostname’s Routing tab to change its action, proxy upload limit, or restore a saved version. Static site deployments have their own Pages view.

GET /domain/routes returns { success: true, data: [...] }. Each route includes hostname, owner_type, owner_id, namespace, status, slug and route_type. Use owner_type to identify a Page, workspace, proxy or tunnel; route_type describes the edge handler and can change when path rules are configured. Disabled routes are included. Account credentials can filter with ?namespace=production; namespace credentials are pinned to their namespace. Database failures return an error, not a successful empty list.

Hostname actions

Use one generic action for each registered hostname: proxy, static, or redirect. Your application chooses policies such as www → apex. Oblien manages hostname ownership, TLS, upload limits and proxy transport. Existing path rules use the same API and remain supported.

const current = await client.routes.get('example.com');
const result = await client.routes.set('example.com', {
  action: {
    kind: 'redirect',
    to: 'https://www.example.com',
    status: 308,
    preservePath: true,
    preserveQuery: true,
  },
  expectedVersion: current.active_version,
});
// result.publication.state is 'published' or 'pending'.
PUT /domain/routes/example.com
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{"action":{"kind":"redirect","to":"https://www.example.com"},"expectedVersion":null}

Other actions:

await client.routes.set('api.example.com', {
  action: { kind: 'proxy', workspace: 'workspace-id', port: 3000 },
  limits: { maxBodyBytes: 10 * 1024 * 1024 },
});
await client.routes.set('docs.example.com', {
  action: { kind: 'static', page: 'my-docs' },
  spa: false,
});

Register the source hostname through its workspace, Page or proxy first. The hostname and its workspace/static target must belong to the same account and namespace. Namespace keys cannot cross that boundary. External proxy origins use the existing target-verification flow. Redirects require ownership of the source; they can point to an external destination.

action cannot be combined with routes or static. A save replaces the whole configuration. The dashboard requires an explicit choice before replacing an existing configuration that the hostname form cannot represent.

Redirects and TLS

301, 302, 307 and 308 are supported. New hostname actions default to 308, preserving the method, path and query. Set preservePath or preserveQuery to false to replace that part. A configured base path is followed by the request path; queries join with &, and fragments remain last. Existing legacy forward routes retain 302.

For example, destination https://www.example.com/base?fixed=1#section and request /guide?lang=en produce https://www.example.com/base/guide?fixed=1&lang=en#section.

Apex and www are independent hostnames. Both need valid certificates for HTTPS. An HTTP redirect runs after TLS; it cannot repair an invalid source certificate. Direct self-hostname redirects and cycles through registered unconditional redirects are rejected. External or conditional application redirects can still create loops outside Oblien's registry.

Proxy upload limits

const { requestBody } = await client.routes.capabilities();
// requestBody.maxBodyBytes: account ceiling
// requestBody.allowedMaxBodyBytes: supported sizes within that ceiling

REST: GET /domain/routes/capabilities (authenticated).

Current native sizes are 1, 5, 10, 25, 50 and 100 MiB. The default ceiling is 100 MiB. Send bytes in limits.maxBodyBytes; use the returned allowed values when building a selector. This limits request bodies forwarded to an application proxy, including aggregate chunked uploads. It does not set a static-file size or a CDN upload quota; CDN uploads keep their signed token and file limits.

Oversized requests do not reach the application. The branded public response uses X-Oblien-Error-Status: 413 with HTTP 200, as described in public route failures. Unsupported sizes return API 400; a value above the account ceiling returns API 403. Rollback checks the current ceiling again. Operator default changes apply to future saves; existing published routes require a resave to change their limit.

Existing path rules

Attach an ordered routing table to any deployment's domain, evaluated at the edge: static assets at /, a same‑origin backend reverse‑proxied at configured paths (/api/*), plus redirects, response headers, SPA fallback, and clean‑URL / trailing‑slash control. It's deployment‑shaped, not Pages‑only:

  • Static site — a Page serves / (set static: { page }).
  • Server‑only — no static; a catch‑all proxy sends everything to a VM.
  • Monorepo — one domain: static at / + /api/* proxied to a VM.
  • Compose — one domain fronting several VM origins by path.

Changes are versioned and require no workload redeploy. This is a constrained path-rule format; it does not interpret a complete vercel.json.

Rules are evaluated before static files ("beforeFiles"): a matching rule wins even if a file exists at that path (so /api/* proxies regardless). Don't add a catch‑all /(.*) → /index.html rewrite — it shadows real assets. For SPA fallback set spa: true.

Set routes

Replaces the saved rule table in one SQL transaction and queues its publication. Each successful save creates a new version; edge propagation is asynchronous.

// Monorepo: static frontend + API proxy on one domain
await client.routes.set('app.example.com', {
  static: { page: 'my-frontend' },        // serves / from this Page (omit for server-only)
  spa: true,
  routes: [
    { match: { path: '/old', type: 'exact' },
      action: { kind: 'redirect', status: 308, to: '/new' } },
    { match: { path: '/api/', type: 'prefix' },
      action: { kind: 'proxy', workspace: 'ws_backend', port: 3000, stripPrefix: true } },
    { match: { path: '/', type: 'prefix' },
      action: { kind: 'headers', set: [{ key: 'X-Frame-Options', value: 'DENY' }] } },
  ],
});
PUT /domain/routes/{hostname}
curl -X PUT "https://api.oblien.com/domain/routes/app.example.com" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "routes": [
      { "match": { "path": "/api/", "type": "prefix" },
        "action": { "kind": "proxy", "origin": "https://backend", "stripPrefix": true } }
    ]
  }'

The hostname must be a domain you own (any routed deployment — Page, workspace/VM, edge‑proxy). Pages also expose a convenience wrapper PUT /pages/{slug}/routes that defaults static to the Page and applies to its managed hostname and current custom apex, excluding a separate www alias. Use /domain/routes/{hostname} for an independent redirect.

Body

FieldTypeDescription
routesRule[]Ordered rule table (full replace, no merge).
actionproxy, static or redirect actionAlternative to a rule table.
expectedVersionnumber or nullCompare-and-save guard; null expects an unversioned route.
limits{ maxBodyBytes }Native application-proxy request-body limit.
static{ page }Static artifact backing / and rewrites. Omit for server‑only.
spabooleanServe index.html on a static miss. Default true.
cleanUrlsbooleanStrip .html, serve extensionless.
trailingSlash"enforce" | "strip"Trailing‑slash policy.

A rule is { match, action }. match = { path, type: "exact"|"prefix"|"wildcard" } (type inferred if omitted; wildcard supports *, (.*), :name, :name* with $1..$9).

actionshapenotes
proxy{ origin | workspace+port, stripPrefix?, path? }Same‑origin reverse proxy (see below).
rewrite{ to }Serve a different asset, URL unchanged. $1..$9 supported.
redirect{ status?, to, preservePath?, preserveQuery? }301/302/307/308 (default 308). $1..$9 supported.
headers{ set: [{ key, value }] }Add/override response headers (non‑terminal).

Evaluation order

  1. Headers — every matching headers rule applies (non‑terminal).
  2. Terminal rules — proxy/rewrite/redirect in array order, first match wins.
  3. Filesystem — only if no rule matched: serve the file (when static is set); on miss → index.html if spa, else 404. cleanUrls/trailingSlash canonicalize here.

Proxy origin

Provide either a verified external origin URL or an owned Oblien workspace + port. An external origin is SSRF‑validated, ownership‑verified, and pinned to its IP; a workspace is resolved to its internal IP:port (use a permanent / static‑network workspace so the pin stays valid). Arbitrary/unverified origins are rejected.

  • stripPrefix: true — a /api/ prefix match forwards /api/users as /users.
  • path — a capture‑templated upstream path (/api/:path* → path "/$1"), the general form of keep/strip. Only the path is templated; the origin host stays pinned.
  • Forwarding: Host = original request host; X‑Forwarded‑For/Proto + X‑Real‑IP added; WebSocket upgrade forwarded.
  • Captures ($1..$9) substitute into rewrite.to, redirect.to, and proxy path.

Read / roll back

GET  /domain/routes/{hostname}            → { active_version, config, versions }
POST /domain/routes/{hostname}/rollback   → { version } re-applies a prior version
const saved = await client.routes.get('app.example.com');
const prior = saved.versions.find(v => v.version !== saved.active_version);
if (prior) await client.routes.rollback('app.example.com', prior.version);

Publication and recovery

The configuration, revision and publication intent commit together. If publication is temporarily unavailable, the API returns 202, success: true, and publication.state: "pending". The saved change is retained and retried automatically; do not create another configuration just to retry delivery.

GET and mutation responses include:

{
  "publication": {
    "operationId": "publication-id",
    "state": "published",
    "attempts": 1,
    "publishedAt": "2026-10-11T12:00:00.000Z"
  }
}

published means the registry accepted the change, not that every edge has already installed it. Normal accepted publication returns 200. Poll GET while pending. The previous route can remain active during an outage. SQL and the edge registry are separate systems; propagation is eventual.

Recovery reads current ownership and targets, so an old retry cannot overwrite a new owner, moved workspace or deletion. Disabled routes remain disabled when edited. History belongs to the route registration; reclaiming a hostname does not grant access to its previous owner's revisions.

Use expectedVersion when saving from an editor. A stale version returns 409 route_version_conflict. Rollback creates a new version, preserves history, and checks current origin ownership and upload limits. Legacy origins without a retained workspace or verified URL return 409 route_origin_refresh_required and must be configured explicitly again. GET includes route registration metadata and staticPage when the static source still belongs to this namespace.

Limits & validation

Rules are untrusted repo input — validated on write, re‑checked at the edge:

  • ≤ 64 rules, ≤ 32 header rules (≤ 32 entries each), pattern ≤ 512, targets ≤ 2048, header value ≤ 1024, total ≤ 32 KB. Over any limit → whole request rejected (400).
  • Patterns compile from constrained tokens, with at most nine captures and bounded edge matching. There is no raw-regex API. .., CRLF, protocol‑relative / non‑http(s) redirects, and hop‑by‑hop / Host headers rejected.

Deployments with no rule table keep serving via their simple built‑in route — you opt into rules only when needed.

Responsibility boundary

Conditional method/header/query matching, has/missing, scripts and response transformations are not supported. Unknown configuration fields and match conditions are rejected rather than silently widening a route. Keep application routing and authentication in the workload. Existing response-header and path rules remain available. WebSocket and SSE transport use the shared proxy.

Normal route lookup and simple action selection use local caches; there are no per-request database or billing checks. Cold registry reads are coalesced.