Domains
Connect a custom domain to a workspace with managed SSL. Routes are registered immediately; SSL becomes active once the domain resolves to an approved edge target. For an overview of all domain options, see the Domains guide.
Hostname redirects and routing
Open Dashboard → Routes → hostname → Routing to select a proxy, static Page or redirect, set an application upload limit, and view publication/version history. The Routes API provides the same controls for resellers.
Apex and www are separate registrations and each HTTPS hostname needs a valid
certificate. To redirect apex to www (or the reverse), configure a redirect on the
source hostname and keep the destination serving the application. No implicit
www stripping occurs. The existing include_www option creates a www → apex
forward; it cannot be combined with an opposing apex → www redirect.
A successful save can return publication.state: "pending" while automatic
registry delivery retries. Consult the routing status separately from TLS status.
Get domain
Get the current custom domain configuration for a workspace.
const domain = await ws.domains.get('ws_a1b2c3d4');
console.log(domain);
// {
// customDomain: "app.example.com",
// port: 3000,
// sslStatus: "pending",
// sslExpiry: null,
// sslError: "DNS is not pointed to edge yet",
// includeWww: false
// }GET /workspace/:workspaceId/domaincurl "https://api.oblien.com/workspace/ws_a1b2c3d4/domain" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"Response
{
"success": true,
"domain": {
"customDomain": "app.example.com",
"port": 3000,
"sslStatus": "pending",
"sslExpiry": null,
"sslError": "DNS is not pointed to edge yet",
"includeWww": false
}
}Returns "domain": null if no custom domain is connected.
Check DNS
Verify DNS records are correctly configured before connecting. This is a dry-run — no changes are made.
const check = await ws.domains.checkDNS('ws_a1b2c3d4', {
domain: 'app.example.com',
});
if (check.verified) {
console.log('DNS is ready — CNAME and ownership verified');
} else {
console.log('Issues:', check.errors);
}POST /workspace/:workspaceId/domain/check{
"domain": "app.example.com"
}curl -X POST "https://api.oblien.com/workspace/ws_a1b2c3d4/domain/check" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{ "domain": "app.example.com" }'Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
domain | string | Yes | The domain to verify |
Response
{
"success": true,
"domain": "app.example.com",
"enterprise": false,
"verified": true,
"cname": true,
"ownership": true,
"records": {
"CNAME": ["edge.oblien.com"],
"TXT": ["verify=ws_a1b2c3d4"]
},
"errors": [],
"required_records": {
"cname": { "host": "app.example.com", "target": "edge.oblien.com" },
"txt": { "host": "_oblien.app.example.com", "value": "verify=ws_a1b2c3d4" }
},
"edge_ips": ["65.109.38.240"]
}DNS records required
Before connecting, add these DNS records at your domain provider:
| Type | Name | Value | Purpose |
|---|---|---|---|
CNAME | app (or @ for root) | edge.oblien.com | Route traffic to Oblien edge |
TXT | _oblien.app.example.com | verify=ws_a1b2c3d4 | Prove domain ownership |
If using Cloudflare with proxy enabled (orange cloud), set an A record pointing to 65.109.38.240 instead of a CNAME. The TXT ownership record is still required.
Enterprise custom-domain grants can connect with edge DNS only. Those accounts may also use a branded TXT prefix such as _opsh instead of _oblien.
Connect domain
Connect a custom domain to a workspace. Oblien verifies ownership rules, registers the route immediately, and requests SSL if the domain is already pointed at an approved edge target.
const result = await ws.domains.connect('ws_a1b2c3d4', {
domain: 'app.example.com',
port: 3000,
include_www: false,
});
console.log(result.url);
// "https://app.example.com"POST /workspace/:workspaceId/domain{
"domain": "app.example.com",
"port": 3000,
"include_www": false
}curl -X POST "https://api.oblien.com/workspace/ws_a1b2c3d4/domain" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{ "domain": "app.example.com", "port": 3000 }'Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
domain | string | Yes | — | Domain to connect (e.g. example.com, app.example.com) |
port | number | No | 3000 | Workspace port to route traffic to (1–65535) |
include_www | boolean | No | false | Also redirect www. to the domain |
Response
{
"success": true,
"domain": "app.example.com",
"port": 3000,
"ssl": {
"status": "pending",
"expiresAt": null,
"error": "A/AAAA resolves to 104.21.1.12, 172.67.2.34 — not a known edge IP. Either CNAME to edge.oblien.com or point A record to one of: 65.109.38.240"
},
"dns": {
"verified": false,
"cname": false,
"ownership": true,
"errors": [
"A/AAAA resolves to 104.21.1.12, 172.67.2.34 — not a known edge IP. Either CNAME to edge.oblien.com or point A record to one of: 65.109.38.240"
]
},
"url": "https://app.example.com"
}What happens
- DNS verification — checks ownership requirements and current edge DNS signal
- Route registration — writes the domain→workspace mapping to etcd immediately
- SSL request — reuses or requests a certificate when the domain already resolves to an approved edge target
- Config storage — saves domain config, including
ssl.status,ssl.expiresAt, and optionalssl.error
If ownership is valid but edge DNS is not ready yet, the connect request can still succeed with ssl.status: "pending". Run renew SSL later or wait for propagation.
Errors
| Code | Status | Cause |
|---|---|---|
invalid_domain | 400 | Domain format is invalid |
invalid_port | 400 | Port out of range (1–65535) |
domain_in_use | 409 | Domain is connected to another workspace |
dns_not_configured | 400 | Standard plans: TXT ownership missing or wrong. Enterprise: no valid edge DNS signal yet |
ssl_failed | 500 | SSL certificate provisioning failed |
vm_not_running | 409 | Workspace VM has no IP (not running) |
Disconnect domain
Remove a custom domain from a workspace. Deletes the routing rule and clears the active SSL material from the edge.
await ws.domains.disconnect('ws_a1b2c3d4');DELETE /workspace/:workspaceId/domaincurl -X DELETE "https://api.oblien.com/workspace/ws_a1b2c3d4/domain" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"Response
{
"success": true,
"removed": "app.example.com"
}Renew SSL
Force SSL certificate renewal for the connected domain. Certificates auto-renew, but you can trigger a manual renewal if needed.
const result = await ws.domains.renewSSL('ws_a1b2c3d4');
console.log(result.ssl.expiresAt);
// "2026-09-08"POST /workspace/:workspaceId/domain/ssl/renewcurl -X POST "https://api.oblien.com/workspace/ws_a1b2c3d4/domain/ssl/renew" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"Response
{
"success": true,
"domain": "app.example.com",
"ssl": {
"status": "active",
"expiresAt": "2026-09-08"
}
}Errors
| Code | Status | Cause |
|---|---|---|
no_domain | 404 | No custom domain connected to this workspace |
ssl_failed | 500 | ACME certificate renewal failed |
Endpoint summary
| Endpoint | Method | Description |
|---|---|---|
/workspace/:id/domain | GET | Get current domain config |
/workspace/:id/domain | POST | Connect a custom domain |
/workspace/:id/domain | DELETE | Disconnect custom domain |
/workspace/:id/domain/check | POST | Verify DNS configuration |
/workspace/:id/domain/ssl/renew | POST | Renew SSL certificate |
/domain/check-slug | POST | Check slug availability |
/domain/verify | POST | Verify custom domain DNS |
Standalone endpoints
These endpoints are not scoped to a specific workspace — use them for pre-flight checks before connecting domains or claiming slugs.
Check slug availability
Check whether a slug is available on a wildcard domain. No DNS check is needed since these are platform-managed subdomains.
POST /domain/check-slug{
"slug": "my-app",
"domain": "preview.oblien.com"
}curl -X POST "https://api.oblien.com/domain/check-slug" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{ "slug": "my-app" }'Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
slug | string | Yes | — | Desired slug (3-48 chars, lowercase alphanumeric + hyphens) |
domain | string | No | preview.oblien.com | Base domain to check against |
Response
{
"success": true,
"slug": "my-app",
"domain": "preview.oblien.com",
"hostname": "my-app.preview.oblien.com",
"url": "https://my-app.preview.oblien.com",
"available": true
}Verify custom domain DNS
Pre-flight DNS check for custom domains. Verifies CNAME or A/AAAA resolution to the edge and, for standard plans, the TXT ownership record.
POST /domain/verify{
"domain": "app.example.com",
"resource_id": "ws_a1b2c3d4"
}curl -X POST "https://api.oblien.com/domain/verify" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{ "domain": "app.example.com", "resource_id": "ws_a1b2c3d4" }'Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
domain | string | Yes | — | Custom domain to verify (e.g. app.example.com) |
resource_id | string | No | — | Workspace ID or page slug for TXT ownership check |
Response
{
"success": true,
"domain": "app.example.com",
"enterprise": false,
"verified": true,
"cname": true,
"ownership": true,
"records": {
"CNAME": ["edge.oblien.com"],
"TXT": ["verify=ws_a1b2c3d4"]
},
"errors": [],
"required_records": {
"cname": { "host": "app.example.com", "target": "edge.oblien.com" },
"txt": { "host": "_oblien.app.example.com", "value": "verify=ws_a1b2c3d4" }
},
"edge_ips": ["65.109.38.240"]
}Enterprise users with custom domains see "enterprise": true — only edge DNS verification is required, and required_records won't include a TXT entry. The TXT host may also use a branded prefix instead of _oblien.
List routes
All registered routes you own — ports, pages, edge proxies, edge tunnels, workspace and custom domains — each with the resource it's bound to (owner_type / owner_id).
GET /domain/routes
GET /domain/routes?namespace=<slug>Namespace scoping. A namespace-scoped API key is pinned to its bound namespace and sees only that namespace's routes — ?namespace cannot widen it. Admin/dashboard callers may pass ?namespace to filter to one of their own namespaces. Results are always scoped to the caller's account.
Response
{
"success": true,
"data": [
{
"hostname": "api.example.com",
"slug": "api",
"domain": "example.com",
"route_type": "host",
"target": "https://203.0.113.45:8080",
"owner_type": "edge_proxy",
"owner_id": "12",
"namespace": "team-a",
"is_custom": true,
"status": "active"
}
]
}List SSL certificates
Your SSL certificates with renewal status — expiry, whether auto-renew is on, and the last renewal error/attempt count (so a failing cert is visible before it expires).
GET /domain/ssls
GET /domain/ssls?namespace=<slug>Same namespace scoping as /domain/routes.
Response
{
"success": true,
"data": [
{
"domain": "app.example.com",
"namespace": "team-a",
"active": true,
"auto_renew": true,
"expires_at": 1789200000000,
"source": "workspace",
"last_attempt_at": "2026-06-18 04:00:11",
"last_error": null,
"renew_attempts": 0
}
]
}Certificates are auto-renewed by a scheduler ~14 days before expiry. last_error / renew_attempts surface a cert whose renewal is failing.
Toggle SSL auto-renew
Enable or disable automatic renewal for a certificate you own. New certificates auto-renew by default.
PUT /domain/ssls/{domain}/auto-renew{ "enabled": false }Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
domain | string (path) | Yes | The certificate's domain |
enabled | boolean | Yes | true to auto-renew, false to opt out |
The cert must be in the caller's account (and namespace, for scoped keys) — otherwise an opaque 404.
Response
{ "success": true, "domain": "app.example.com", "auto_renew": false }