API

DNS API

Connect a DNS provider token so Openship writes your domains' records itself, instead of printing them for you to paste.

Adding a custom domain normally ends with Openship telling you which records to create and waiting for you to go and create them. Connect a DNS provider here and it writes them itself: the domain verifies on its own and gets its certificate without you leaving the page. In the dashboard this is Settings → DNS.

Cloudflare is the only provider today. The provider list is served by the API rather than hard-coded in the dashboard, so GET /api/dns/providers is the authoritative answer to "what can I connect?".

Base path & auth

All paths are relative to your instance, under /api — e.g. https://your-host/api/dns/credentials. Send a personal access token as a bearer header (Authorization: Bearer <token>), created with openship token create. The dashboard uses your session cookie instead. See the API overview for the full auth model.

Endpoints

Method & pathPermissionWhat it does
GET /api/dns/providerssettings:readList supported providers and the token scopes each needs.
GET /api/dns/credentialssettings:readList connected credentials for the organization.
GET /api/dns/credentials/:idsettings:readRead one connected credential.
POST /api/dns/credentialssettings:adminConnect a credential. The token is verified before it is stored.
DELETE /api/dns/credentials/:idsettings:adminDisconnect a credential. Records already written are left alone.
POST /api/dns/verify-zonesettings:readCheck whether a connected provider manages a hostname's zone. Creates and changes nothing.

Connect a provider

POST /api/dns/credentials

Prop

Type

curl -X POST https://your-host/api/dns/credentials \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider":"cloudflare","name":"Cloudflare production","apiToken":"cf-..."}'

For Cloudflare, create a token at dash.cloudflare.com/profile/api-tokens scoped to Zone:Zone:Read and Zone:DNS:Edit, limited to the zones you want Openship to manage.

The token is write-only from here on

It is encrypted at rest (AES-256-GCM, key derived from BETTER_AUTH_SECRET) and no endpoint returns it — not in full and not as a prefix. tokenMasked is a fixed ••••••••. There is no "reveal"; to change a token, disconnect the credential and connect a new one.

Rotating BETTER_AUTH_SECRET makes stored tokens undecryptable. The credential then reports status: "invalid" the next time Openship tries to use it, and you re-connect it.

Check a zone

POST /api/dns/verify-zone

Prop

Type

The response separates four outcomes, because they call for different actions:

statusmatchedMeaning
matchedtrueA connected provider hosts this zone; records for this domain will be written automatically.
nonefalseWe asked, and no connected provider hosts it. You add the records yourself.
unauthorizedfalseA stored token was rejected. Re-connect it — credentialId says which.
unavailablefalseThe provider could not be reached (rate limit, outage). Unknown, not a sign of misconfiguration.
curl -X POST https://your-host/api/dns/verify-zone \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"hostname":"app.example.com"}'

What happens when you add a domain

With a provider connected, POST /api/domains writes the records it would otherwise have only shown you, and the response carries an extra autoDns object reporting what happened per record:

{
  "data": { "id": "dom_123", "hostname": "app.example.com" },
  "records": { "mode": "selfhosted", "records": [{ "type": "A", "name": "app.example.com", "value": "203.0.113.10" }] },
  "autoDns": {
    "provisioned": true,
    "records": [{ "name": "app.example.com", "type": "A", "outcome": "applied" }]
  }
}

autoDns is absent when no connected provider manages the zone — that absence is the signal to show the records for the operator to add by hand. When it is present, provisioned is true only if every record landed; a partial write reports provisioned: false with a reason and the per-record outcome, so "we wrote them" and "we wrote some of them" are never the same answer.

No verification is triggered at that moment. The records are seconds old, a resolver may still be holding a negative answer for the name, and a failed check burns one of Let's Encrypt's per-hostname validation failures. The domain stays pending and the domains:verify-pending sweep picks it up after its ten-minute grace window.

Removing records

Disconnecting a credential does not touch DNS. Removing a domain does — but only records Openship created, identified by the Managed by Openship comment it writes on every record.

Why ownership is tracked, not inferred

For an apex domain the record name is the zone apex, where your MX, SPF TXT and CAA records also live. Deleting "every record at this name" would take your mail with it, so Openship deletes only what it can prove it wrote.

Connecting a domain does repoint an existing record at that name — that is what connecting it means — but repointing is not adoption: Openship leaves such a record's comment untouched, so it never carries the Managed by Openship marker and removing the domain later will not delete it.

Errors

CodeStatusMeaning
DNS_UNKNOWN_PROVIDER400provider is not a name from GET /api/dns/providers.
DNS_PROVIDER_NOT_READY400The provider rejected the token during the pre-store check.
DNS_RECORD_CONFLICT409Several records already answer for that name and type and none are Openship's — a round-robin or multi-host set it will not rewrite.
DNS_API_ERROR502The provider's API failed. The upstream status is in the message.

On this page