Domains and TLS
Attach hostnames, verify DNS, and manage certificates.
Domains connect hostnames to a project and track DNS verification and TLS certificates. The SDK takes a project ID as the first argument when creating or listing domains; REST includes it in the body or query.
Add a domain
const result = await ship.domains.create("proj_123", {
hostname: "app.example.com",
});
console.log(result);curl -X POST "$OPENSHIP_URL/api/domains" \
-H "Authorization: Bearer $OPENSHIP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"projectId":"proj_123","hostname":"app.example.com"}'Prop
Type
Adding a domain doesn't make it live by itself
After adding, you must point your DNS at Openship and then verify. Use POST /api/domains/preview
(or openship domain preview <hostname>) to see the exact records to add at your DNS provider first.
Preview required DNS
Side-effect-free: pass a hostname, get back the records to create. POST is used only so the hostname
travels in the body.
const result = await ship.domains.preview({
hostname: "app.example.com",
});
console.log(result);curl -X POST "$OPENSHIP_URL/api/domains/preview" \
-H "Authorization: Bearer $OPENSHIP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"hostname":"app.example.com"}'The response's mode tells you which setup applies: selfhosted (an A record to your server), cloud
(a CNAME to Openship Cloud), or external (a TXT record only — you front TLS yourself).
Verify a domain
const verification = await ship.domains.verify("dom_123");
console.log(verification);curl -X POST "$OPENSHIP_URL/api/domains/dom_123/verify" \
-H "Authorization: Bearer $OPENSHIP_TOKEN"Once DNS is in place, verify it. Openship Cloud checks its routing and ownership records. Self-hosted verification obtains or reuses the certificate on the deployment server. Domains with external ingress keep TLS at their upstream proxy.
Wildcard HTTPS and manual TXT
Wildcard and DNS-01 domains on self-hosted targets use the same operations in the dashboard, SDK and MCP. Deploy and enable the project first. An external ingress or uploaded certificate keeps its existing TLS flow.
// "automatic" uses a connected DNS provider. "manual" generates a real ACME TXT.
const attempt = await ship.domains.startDnsChallenge("dom_123", { mode: "manual" });
const current = await ship.domains.dnsChallenge("dom_123");
// Once current.status is "waiting", publish current.record at the DNS provider.
// Then explicitly ask Openship to check it:
await ship.domains.checkDnsChallenge("dom_123", { attemptId: attempt.id });Start and check return 202 with { data: attempt }; they do not hold the HTTP request open for
certificate issuance. Read GET /api/domains/:id/dns/challenge until the result settles. It returns
{ data: null } before any attempt. The safe attempt includes id, domainId, mode, status,
record, expiresAt, logs, error, createdAt and updatedAt. It never includes private keys,
account credentials or serialized ACME orders.
preparing, checking, installing and cancelling represent running work. waiting needs the operator's
TXT; stop polling until they act. completed, failed, cancelled and expired are settled results.
A missing TXT or retryable validation/install error keeps the same order in waiting with its error.
Repeated starts reuse an active attempt; check and cancel require its current attemptId and reject
a stale ID with 409. An installation that has already started cannot be cancelled.
Manual renewal uses startDnsChallenge(id, { mode: "manual", force: true }) and a fresh TXT value.
cancelDnsChallenge(id, { attemptId }) discards an unfinished manual attempt without removing existing TLS.
Connected-provider mode renews automatically when the SSL job is enabled. See the
wildcard guide for DNS names, record cleanup and renewal steps.
Errors
422 — verification failed
The DNS records aren't resolving yet, or don't match. DNS can take minutes to hours to propagate. Confirm
the records match preview exactly, then retry. The response's cnameVerified / txtVerified flags tell
you which check is still failing.
404 on a per-domain route
The :id doesn't belong to your organization (or was deleted). List your domains with GET /api/domains
to get valid IDs.
Operations
| Operation | SDK | REST API |
|---|---|---|
| Stream domain verification progress. | domains.verifyStream(id, input?, options?) | POST /api/domains/:id/verify/streamdomain:write |
| List domains using the supplied filters. | domains.list(id) | GET /api/domainsdomain:list |
| Attach a domain using the requested project and routing configuration. | domains.create(id, input) | POST /api/domainsdomain:write |
| Read one domain record. | domains.get(id) | GET /api/domains/:iddomain:read |
| Remove a domain and its routing binding. | domains.remove(id) | DELETE /api/domains/:iddomain:admin |
| Run domain verification. | domains.verify(id, input?) | POST /api/domains/:id/verifydomain:write |
| Read DNS records required by a domain. | domains.records(id, input?) | GET /api/domains/:id/recordsdomain:read |
| Preview DNS changes before applying them. | domains.dnsPlan(id, input?) | GET /api/domains/:id/dns/plandomain:read |
| Apply the authorized DNS plan. | domains.dnsApply(id, input?) | POST /api/domains/:id/dns/applydomain:write |
| Read the saved DNS certificate attempt, TXT record, deadline and logs. | domains.dnsChallenge(id) | GET /api/domains/:id/dns/challengedomain:read |
| Start automatic provider setup or a manual TXT challenge; reuse an active attempt. Self-hosted targets only. | domains.startDnsChallenge(id, input) | POST /api/domains/:id/dns/challengedomain:write |
| Check the saved attempt's TXT, complete ACME validation and install HTTPS on the serving host. | domains.checkDnsChallenge(id, input) | POST /api/domains/:id/dns/challenge/checkdomain:write |
| Cancel a manual DNS certificate attempt before installation starts; retain existing certificates. | domains.cancelDnsChallenge(id, input) | POST /api/domains/:id/dns/challenge/canceldomain:write |
| Select the project’s primary domain. | domains.setPrimary(id) | POST /api/domains/:id/primarydomain:write |
| Request certificate renewal for a domain record. | domains.renewSsl(id) | POST /api/domains/:id/renewdomain:write |
| Check the domain’s current certificate. | domains.verifySsl(id) | POST /api/domains/:id/verify-ssldomain:write |
| Install a supplied certificate and private key. | domains.uploadCert(id, input) | POST /api/domains/:id/certificatedomain:write · Self-hosted |
| Preview domain connection requirements. | domains.preview(input) | POST /api/domains/previewdomain:read |
| Request renewal for eligible certificates. | domains.renewAllSsl() | POST /api/domains/renew-alldomain:write |
| Verify pending domains visible to the caller. | domains.verifyPending(input?) | POST /api/domains/verify-pendingdomain:write |