OpenCoreDev Releases Domain SDK 0.2.0: One TypeScript API to Add, Verify, and Remove Customer Domains Across Five Platforms

Disclosure: Some links in this article are affiliate links. AI Maestro may earn a commission if you make a purchase, at no…

By Vane July 14, 2026 3 min read
OpenCoreDev Releases Domain SDK 0.2.0: One TypeScript API to Add, Verify, and Remove Customer Domains Across Five Platforms

OpenCoreDev has released Domain SDK version 0.2.0, a TypeScript library that standardises how developers add, verify, and remove customer domains across five hosting platforms.

The scope

The tool handles Vercel, Cloudflare for SaaS, Railway, Render, and Netlify. It does not register domains, host DNS, or store tenant data. Your application retains all authorisation logic. The provider remains the source of truth.

The package runs server-side only to keep credentials out of browser code. It requires Node version 20 or higher and supports Bun. The distribution is ESM-only with a single runtime dependency: tldts.

The client interface

The API consists of seven methods available on a stateless client returned by createDomainClient(): add, get, refresh, list, verify, remove, and waitUntilActive. Every method accepts an AbortSignal. The add and remove functions are idempotent, meaning retries are safe.

Each platform is a separate entry point: ./vercel, ./cloudflare, ./railway, ./render, and ./netlify. A testing adapter is also included. Switching platforms requires changing the import statement, not the workflow.

Status is granular

Because DNS propagation, ownership checks, and TLS certificate issuance happen at different speeds, the SDK models these states separately. DomainStatus contains eight values:

pending, pending_dns, pending_verification, pending_certificate, active, misconfigured, failed, and unknown.

A Domain object also holds separate fields for verification (pending | verified | failed | unknown), certificate (pending | active | expiring | failed | unknown), and an issues array. Each issue includes a code, message, an optional record, and a retryable flag.

Record types follow the same pattern. DnsRecord lists type, name, value, an optional ttl, purpose, required, status, and an optional description. The purpose field is routing, ownership, certificate, or other. The status field is pending, valid, invalid, or unknown.

Consequently, a Vercel hostname surfaces three distinct records:

TypeNamePurpose
CNAMEapp.customer.comrouting
TXT_vercel.app.customer.comownership
TXT_acme-challenge.app.customer.comcertificate

Platform limits

While the lifecycle is normalised, platform restrictions remain:

CapabilityVercelCloudflare SaaSRailwayRenderNetlify
List domains
Explicit verification
Managed certificates
Apex domains
Wildcard domains
Automatic customer DNS

Cloudflare for SaaS requires a CNAME target, so the adapter does not claim apex support. Render alone accepts wildcard hostnames. No adapter edits customer DNS automatically.

Instead of hard-coding these limits, read client.capabilities at runtime. It exposes list, explicitVerification, managedCertificates, apexDomains, and wildcardDomains.

Working with the client

The following snippet type-checks and runs against 0.2.0:

import { createDomainClient } from "@opencoredev/domain-sdk";
import { vercel } from "@opencoredev/domain-sdk/vercel";

export const domains = createDomainClient({
  provider: vercel({
    token: process.env.VERCEL_TOKEN!,
    projectId: process.env.VERCEL_PROJECT_ID!,
  }),
});

const domain = await domains.add("app.customer.com");

// Render every required record in your customer-facing DNS instructions.
const required = domain.records.filter((item) => item.required);

const active = await domains.waitUntilActive(domain.hostname, {
  timeoutMs: 300_000,          // default
  intervalMs: 5_000,           // default, minimum 250
  onStatus: (current) => console.log(current.status),
});

console.log(active.certificate.status, active.verification.status);

Polling is sequential. It defaults to a 300,000 ms timeout and a 5,000 ms interval. Intervals below 250 ms are rejected, and each state is reported through onStatus. When a provider returns retryAfter, the client waits at least that long before retrying.

Testing and agent support

For CI, an in-memory adapter never calls a real provider:

import { createDomainClient } from "@opencoredev/domain-sdk";
import { memoryProvider } from "@opencoredev/domain-sdk/testing";

const memory = memoryProvider();
const domains = createDomainClient({ provider: memory });

await domains.add("app.customer.com");   // status: pending_dns
memory.activate("app.customer.com");

const latest = await domains.refresh("app.customer.com");
console.log(latest.status === "active"); // true

It also supports transition(), injected latencyMs, per-operation failures, and a calls log. createMockDomain, createMockDnsRecord, and createFailingProvider sit alongside it.

Separately, an agent skill installs the workflow into Codex, Claude Code, Cursor, and other compatible agents:

npx skills add opencoredev/domain-sdk --skill domain-sdk

Use cases

Scroll to Top