BlueForge Docs

Domain Management

Domain management handles DNS verification, SSL provisioning via Cloudflare, and per-project domain attachments with automated TXT record checks.

Endpoint

Standalone domain ops (
/api/domains
, viewer role)

MethodPathAuthIdempotent
GET
/api/domains/:id
viewer Beareryes
POST
/api/domains/:id/verify
viewer Bearerno
POST
/api/domains/:id/provision-ssl
viewer Bearerno
POST
/api/domains/:id/refresh
viewer Beareryes
PATCH
/api/domains/:id
viewer Bearerno
DELETE
/api/domains/:id
viewer Beareryes

Project-scoped domain ops (
/api/projects/:slug/domains
, viewer role)

MethodPathAuthIdempotent
GET
/api/projects/:slug/domains
viewer Beareryes
POST
/api/projects/:slug/domains
viewer Bearerno

Parameters

NameTypeRequiredDefaultNotes
id
stringyes (path)Domain UUID
slug
stringyes (path)Project slug
domain
stringyes (POST body)Domain name to attach (e.g.
"example.com"
)
originServer
stringno (PATCH body)Origin server address for the domain

Response

// GET /api/domains/:id — domain details
{
  id: string;
  domain: string;
  verificationStatus: "pending" | "verified" | "failed";
  verificationToken: string;
  sslStatus: "none" | "provisioning" | "active" | "failed";
  sslExpiresAt: string | null;
  originServer: string | null;
  projectId: string;
  createdAt: string;
  updatedAt: string;
}

// POST /api/domains/:id/verify — DNS verification result
{
  verified: boolean;
  domain: { /* domain object — updated if verified */ };
}

// POST /api/domains/:id/provision-ssl — SSL provisioning result
{
  id: string;
  sslStatus: "provisioning" | "active";
  sslExpiresAt: string | null;
  // ... other domain fields ...
}

// GET /api/projects/:slug/domains — list project domains
Array<{
  id: string;
  domain: string;
  verificationStatus: string;
  sslStatus: string;
  createdAt: string;
}>

// POST /api/projects/:slug/domains (201) — attached domain
{
  id: string;
  domain: string;
  verificationStatus: "pending";
  verificationToken: string;
  projectId: string;
  createdAt: string;
}

// DELETE /api/domains/:id — delete confirmation
{ success: true }

// Error responses
{ error: "Domain not found" }                                   // 404
{ error: "Missing project slug" }                               // 400
{ error: "domain is required" }                                 // 400
{ error: "Domain must be DNS-verified before provisioning SSL" } // 400
{ error: "SSL provisioning failed" }                            // 500

Examples

# Get domain details (includes verification token)
curl -H "Authorization: Bearer $BF_API_KEY" \
  https://api.blueforge.studio/api/domains/dmn_xxx

# Verify DNS TXT record
curl -X POST https://api.blueforge.studio/api/domains/dmn_xxx/verify \
  -H "Authorization: Bearer $BF_API_KEY"

# Provision SSL (domain must be verified first)
curl -X POST https://api.blueforge.studio/api/domains/dmn_xxx/provision-ssl \
  -H "Authorization: Bearer $BF_API_KEY"

# Refresh SSL status from Cloudflare
curl -X POST https://api.blueforge.studio/api/domains/dmn_xxx/refresh \
  -H "Authorization: Bearer $BF_API_KEY"

# Update origin server
curl -X PATCH https://api.blueforge.studio/api/domains/dmn_xxx \
  -H "Authorization: Bearer $BF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"originServer": "192.168.1.1"}'

# Delete domain
curl -X DELETE https://api.blueforge.studio/api/domains/dmn_xxx \
  -H "Authorization: Bearer $BF_API_KEY"

# List domains for a project
curl -H "Authorization: Bearer $BF_API_KEY" \
  https://api.blueforge.studio/api/projects/my-app/domains

# Attach a new domain to a project
curl -X POST https://api.blueforge.studio/api/projects/my-app/domains \
  -H "Authorization: Bearer $BF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'
import { Client } from "@blueforge/client"

const client = new Client({ apiKey: process.env.BF_API_KEY })

// Get domain details
const domain = await client.get(`/api/domains/${domainId}`)

// Verify DNS TXT record
const { verified } = await client.post(`/api/domains/${domainId}/verify`)

// Provision SSL
const ssl = await client.post(`/api/domains/${domainId}/provision-ssl`)

// List project domains
const domains = await client.get(`/api/projects/my-app/domains`)

// Attach domain
const newDomain = await client.post(`/api/projects/my-app/domains`, {
  domain: "example.com",
})

Pitfalls

  • SSL provisioning requires the domain to be DNS-verified first (
    verificationStatus === "verified"
    ). Posting to
    /provision-ssl
    on an unverified domain returns a 400 error.
  • DNS verification checks for a
    _blueforge-verification
    TXT record. You must add this record to your DNS provider before calling the verify endpoint.
  • SSL provisioning uses Cloudflare and requires the
    CLOUDFLARE_API_TOKEN
    environment variable to be set on the hosting-api server.
  • Error messages from SSL provisioning have bearer tokens redacted (
    Bearer [REDACTED]
    ) to prevent credential leakage in error responses.
  • Project-scoped domain ops resolve the project by slug via
    getDefaultAppByProjectSlug
    . If the project has no default app, the route returns 404 even though the project exists.
  • The
    _blueforge-verification
    TXT record name is specific to this codebase. Other verification systems (e.g.,
    _mailstack-verification
    ) use different record names.

See also

Tested against

  • @blueforge/hosting-api: 2.4.1
  • Last verified: 2026-07-15