API Reference

Routes

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.

Maps 1:1 to the routing config in vercel.json. Changes are versioned and go live in under a second with no redeploy.

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

Atomically replaces a hostname's full ordered rule table. Each call creates a new version.

// 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 all its hostnames.

Body

FieldTypeDescription
routesRule[]Ordered rule table (full replace, no merge).
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 }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 rulesproxy/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 { active_version } = await client.routes.get('app.example.com');
await client.routes.rollback('app.example.com', active_version - 1);

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 (422).
  • Patterns compiled server‑side (no raw regex → no ReDoS). .., 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.

Not yet supported

Conditional matching (has/missing), method‑based matching, and CDN‑bundle static sources are planned.