Skip to content

Domains API

Service domains.

10 operations. Every call needs a session cookie or an Authorization: Bearer nct_… token unless marked Public. Conventions, errors and scopes

PATCH/domains/{domainId}

Update a domain's routing rule (staged until deployed)

Requires If-Match: "<generation>" of the owning service; a stale value returns 412 generation_conflict. Hostname changes need projects:write; every other field (path prefix, strip/add prefix, redirect, TLS mode, pinned certificate, options) also needs proxy:write and an agent with proxy v2 (422 agent_too_old). 422 codes: invalid_path_prefix, invalid_redirect, redirect_loop, invalid_tls_mode, certificate_mismatch, invalid_options, basic_auth_needs_users. 409 domain_conflict when host+path is taken.

ParameterInTypeDescription
domainIdrequiredpathstring
If-MatchheaderstringThe owning service's ETag (generation) from its last GET. Required.
Request body · application/json · required
UpdateInputBody
  • addPrefixstring
    Prepend this path before forwarding to the service
  • certificateIdstring
    An uploaded certificate of the service's server whose names cover the hostname; empty clears the pin
  • dnsResolverstring
    DNS-01 provider (a lego provider code) that obtains the certificate; the server must have a resolver for it. Empty = HTTP-01 or a covering wildcard certificate
  • hostnamestring
  • kindenum: serve | redirect
    serve routes to the service; redirect answers with a redirect to redirectTo
  • RouteOptions
  • pathPrefixstring
    Serve only this path (and below), e.g. /api. Empty = the whole host
  • preservePathboolean
    A redirect keeps the request path and query (default true)
  • redirectCodeenum: 301 | 302 | 307 | 308
  • redirectTostring
    Target hostname of a redirect (not a URL)
  • stripPrefixboolean
    Remove the path prefix before forwarding to the service
  • tlsModeenum: auto | custom | none
    auto = ACME or the default certificate, custom = an uploaded certificate, none = plain HTTP only
  • wwwRedirectboolean
    true creates (false removes) a whole-host 301 redirect from the www/apex counterpart of this hostname (www.<host>, or the host without www.) to this domain. Both hostnames are routed and get certificates; each needs its own DNS record. Needs a whole-host serve domain (422 invalid_www_redirect)
Response 200 · OK · application/json
DomainView
  • addPrefixstringrequired
  • authUsersstring[] | nullrequired
  • DomainCertrequired
  • certificateIdstringrequired
  • componentstringrequired
  • DomainDNSrequired
  • dnsCheckedAtstring (date-time) | nullrequired
  • dnsOkboolean | nullrequired
  • dnsResolverstringrequired
    The lego code of the DNS-01 provider that obtains the certificate; empty = HTTP-01 or a covering wildcard certificate
  • generatedbooleanrequired
  • hostnamestringrequired
  • idstringrequired
  • kindenum: serve | redirectrequired
  • RouteOptionsrequired
  • pathPrefixstringrequired
  • preservePathbooleanrequired
  • redirectCodeenum: 301 | 302 | 307 | 308required
  • redirectTostringrequired
  • statusstringrequired
  • statusMessagestringrequired
  • stripPrefixbooleanrequired
  • targetPortinteger (int64)required
  • tlsModeenum: auto | custom | nonerequired
  • wwwPartnerstringrequired
    The counterpart hostname a www redirect would use (www.<host>, or the host without www.); empty when the domain cannot have one
  • wwwRedirectbooleanrequired
    A whole-host redirect from wwwPartner to this domain exists (both hostnames are routed and get certificates)
  • wwwRedirectForstringrequired
    For the redirect domain of a www redirect: the hostname it redirects to; empty otherwise
Errors
  • 401Unauthorized
  • 403Forbidden
  • 404Not Found
  • 409Conflictdomain_conflict
  • 412Precondition Failedgeneration_conflict
  • 422Unprocessable Entityagent_too_old
  • 428Precondition Required
  • 500Internal Server Error

Errors are RFC 9457 application/problem+json with a stable code field.

DELETE/domains/{domainId}

Remove a domain (staged until deployed)

ParameterInTypeDescription
domainIdrequiredpathstring
Response 204 · No Content

No body.

Errors
  • 401Unauthorized
  • 403Forbidden
  • 404Not Found
  • 422Unprocessable Entity
  • 500Internal Server Error

Errors are RFC 9457 application/problem+json with a stable code field.

PUT/domains/{domainId}/auth-users

Replace a domain's basic-auth users

Replaces the whole user set (needs proxy:write). A user that already exists may omit the password to keep it. Passwords are bcrypt-hashed (cost 10) and stored sealed; only user names are ever returned. Enable the option with PATCH /domains/{id} options.basicAuth. Users apply live with the next route push.

ParameterInTypeDescription
domainIdrequiredpathstring
Request body · application/json · required
AuthUserBody[] | null
  • passwordstring
    Omit to keep the password of an existing user
  • usernamestringrequired
Response 200 · OK · application/json
AuthUsersOutputBody
  • usernamesstring[] | nullrequired
Errors
  • 401Unauthorized
  • 403Forbidden
  • 404Not Found
  • 422Unprocessable Entity
  • 500Internal Server Error

Errors are RFC 9457 application/problem+json with a stable code field.

DELETE/domains/{domainId}/auth-users/{username}

Remove one basic-auth user

ParameterInTypeDescription
domainIdrequiredpathstring
usernamerequiredpathstring
Response 204 · No Content

No body.

Errors
  • 401Unauthorized
  • 403Forbidden
  • 404Not Found
  • 422Unprocessable Entity
  • 500Internal Server Error

Errors are RFC 9457 application/problem+json with a stable code field.

GET/domains/{domainId}/dns

Live Cloudflare DNS status of a domain

Reads the records of the hostname from Cloudflare and classifies them: managed (a record NebulaCtrl created points to the server or tunnel), present (a record NebulaCtrl did not create already points there), wrong_target, missing or not_in_zone. Changes nothing.

ParameterInTypeDescription
domainIdrequiredpathstring
Response 200 · OK · application/json
DNSStatus
  • checkedAtstring (date-time) | nullrequired
  • configuredbooleanrequired
    The team has a Cloudflare connection
  • conflictstringrequired
    Why adopting is impossible (for example a CNAME next to an A record)
  • RecordViewrequired
  • effectiveProxiedbooleanrequired
  • errorstringrequired
    The last failed sync, empty when the last sync worked
  • managebooleanrequired
    NebulaCtrl keeps this domain's records in sync
  • messagestringrequired
  • needsAdoptbooleanrequired
  • OriginLockViewrequired
  • proxiedbooleanrequired
    The proxy setting of the domain (orange cloud)
  • RecordView[] | nullrequired
    A, AAAA and CNAME records of the hostname
  • sslModeenum: | off | flexible | full | strictrequired
    The zone's SSL/TLS mode; empty when unknown
  • statusenum: | managed | present | wrong_target | missing | not_in_zonerequired
  • tunnelbooleanrequired
    The server is behind a Cloudflare Tunnel: the record is a proxied CNAME to it
  • warningsstring[] | nullrequired
  • zoneIdstringrequired
  • zoneNamestringrequired
Errors
  • 401Unauthorized
  • 403Forbidden
  • 404Not Found
  • 422Unprocessable Entity
  • 500Internal Server Error
  • 502Bad Gateway

Errors are RFC 9457 application/problem+json with a stable code field.

PUT/domains/{domainId}/dns

Change a domain's Cloudflare settings

Sets the per-domain proxy toggle and whether NebulaCtrl manages the domain's records, then syncs. A failed sync is reported as error in the status and retried in the background.

ParameterInTypeDescription
domainIdrequiredpathstring
Request body · application/json · required
DnsSettingsInputBody
  • manageboolean
    Let NebulaCtrl keep the domain's DNS records in sync
  • originLockboolean
    Lock the origin to Cloudflare while the domain is proxied (default true). False opts out: anyone who finds the server's IP can bypass Cloudflare. Audited.
  • proxiedboolean
    Proxy through Cloudflare (orange cloud). Tunnel records are always proxied.
Response 200 · OK · application/json
DNSStatus
  • checkedAtstring (date-time) | nullrequired
  • configuredbooleanrequired
    The team has a Cloudflare connection
  • conflictstringrequired
    Why adopting is impossible (for example a CNAME next to an A record)
  • RecordViewrequired
  • effectiveProxiedbooleanrequired
  • errorstringrequired
    The last failed sync, empty when the last sync worked
  • managebooleanrequired
    NebulaCtrl keeps this domain's records in sync
  • messagestringrequired
  • needsAdoptbooleanrequired
  • OriginLockViewrequired
  • proxiedbooleanrequired
    The proxy setting of the domain (orange cloud)
  • RecordView[] | nullrequired
    A, AAAA and CNAME records of the hostname
  • sslModeenum: | off | flexible | full | strictrequired
    The zone's SSL/TLS mode; empty when unknown
  • statusenum: | managed | present | wrong_target | missing | not_in_zonerequired
  • tunnelbooleanrequired
    The server is behind a Cloudflare Tunnel: the record is a proxied CNAME to it
  • warningsstring[] | nullrequired
  • zoneIdstringrequired
  • zoneNamestringrequired
Errors
  • 401Unauthorized
  • 403Forbidden
  • 404Not Found
  • 422Unprocessable Entity
  • 500Internal Server Error

Errors are RFC 9457 application/problem+json with a stable code field.

POST/domains/{domainId}/dns/fix

Fix a domain's DNS record

Creates or corrects the record so the hostname points at the server (or tunnel). A record NebulaCtrl did not create is never modified unless adopt is true (409 adoption_required; 409 dns_conflict when adopting is impossible, for example a CNAME next to an A record). 422 not_configured / not_in_zone; 502 when Cloudflare fails.

ParameterInTypeDescription
domainIdrequiredpathstring
Request body · application/json · required
DnsFixInputBody
  • adoptboolean
    Take over the existing record NebulaCtrl did not create: it is marked as managed and pointed at the server
Response 200 · OK · application/json
DNSStatus
  • checkedAtstring (date-time) | nullrequired
  • configuredbooleanrequired
    The team has a Cloudflare connection
  • conflictstringrequired
    Why adopting is impossible (for example a CNAME next to an A record)
  • RecordViewrequired
  • effectiveProxiedbooleanrequired
  • errorstringrequired
    The last failed sync, empty when the last sync worked
  • managebooleanrequired
    NebulaCtrl keeps this domain's records in sync
  • messagestringrequired
  • needsAdoptbooleanrequired
  • OriginLockViewrequired
  • proxiedbooleanrequired
    The proxy setting of the domain (orange cloud)
  • RecordView[] | nullrequired
    A, AAAA and CNAME records of the hostname
  • sslModeenum: | off | flexible | full | strictrequired
    The zone's SSL/TLS mode; empty when unknown
  • statusenum: | managed | present | wrong_target | missing | not_in_zonerequired
  • tunnelbooleanrequired
    The server is behind a Cloudflare Tunnel: the record is a proxied CNAME to it
  • warningsstring[] | nullrequired
  • zoneIdstringrequired
  • zoneNamestringrequired
Errors
  • 401Unauthorized
  • 403Forbidden
  • 404Not Found
  • 409Conflictadoption_requireddns_conflict
  • 422Unprocessable Entitynot_configured
  • 500Internal Server Error
  • 502Bad Gateway

Errors are RFC 9457 application/problem+json with a stable code field.

GET/services/{serviceId}/domains

List a service's domains

ParameterInTypeDescription
serviceIdrequiredpathstring
Response 200 · OK · application/json
ListOutputBody1
  • DomainView[] | nullrequired
Errors
  • 401Unauthorized
  • 403Forbidden
  • 404Not Found
  • 422Unprocessable Entity
  • 500Internal Server Error

Errors are RFC 9457 application/problem+json with a stable code field.

POST/services/{serviceId}/domains

Add a domain (staged until deployed)

Hostnames are RFC 1123, lowercase and globally unique (409 domain_conflict). Fails with 422 proxy_unavailable while the server's proxy is not running. statusMessage carries DNS guidance and dnsOk the result of the periodic DNS check. Compose stacks need component (422 component_required, unknown_component) and, unless the component declares exactly one port, targetPort (422 target_port_required).

ParameterInTypeDescription
serviceIdrequiredpathstring
Request body · application/json · required
CreateInputBody1
  • addPrefixstring
    Prepend this path before forwarding to the service
  • certificateIdstring
    An uploaded certificate of the service's server whose names cover the hostname; empty clears the pin
  • componentstring
    Compose stacks only (required for them): the component (compose service) the domain routes to
  • dnsResolverstring
    DNS-01 provider (a lego provider code) that obtains the certificate; the server must have a resolver for it. Empty = HTTP-01 or a covering wildcard certificate
  • hostnamestringrequired
  • kindenum: serve | redirect
    serve routes to the service; redirect answers with a redirect to redirectTo
  • RouteOptions
  • pathPrefixstring
    Serve only this path (and below), e.g. /api. Empty = the whole host
  • preservePathboolean
    A redirect keeps the request path and query (default true)
  • redirectCodeenum: 301 | 302 | 307 | 308
  • redirectTostring
    Target hostname of a redirect (not a URL)
  • stripPrefixboolean
    Remove the path prefix before forwarding to the service
  • targetPortinteger (int64)
    Defaults to, and must equal, the service port. Compose stacks: the container port of the component; optional when the component declares exactly one port
  • tlsModeenum: auto | custom | none
    auto = ACME or the default certificate, custom = an uploaded certificate, none = plain HTTP only
  • wwwRedirectboolean
    true creates (false removes) a whole-host 301 redirect from the www/apex counterpart of this hostname (www.<host>, or the host without www.) to this domain. Both hostnames are routed and get certificates; each needs its own DNS record. Needs a whole-host serve domain (422 invalid_www_redirect)
Response 201 · Created · application/json
DomainView
  • addPrefixstringrequired
  • authUsersstring[] | nullrequired
  • DomainCertrequired
  • certificateIdstringrequired
  • componentstringrequired
  • DomainDNSrequired
  • dnsCheckedAtstring (date-time) | nullrequired
  • dnsOkboolean | nullrequired
  • dnsResolverstringrequired
    The lego code of the DNS-01 provider that obtains the certificate; empty = HTTP-01 or a covering wildcard certificate
  • generatedbooleanrequired
  • hostnamestringrequired
  • idstringrequired
  • kindenum: serve | redirectrequired
  • RouteOptionsrequired
  • pathPrefixstringrequired
  • preservePathbooleanrequired
  • redirectCodeenum: 301 | 302 | 307 | 308required
  • redirectTostringrequired
  • statusstringrequired
  • statusMessagestringrequired
  • stripPrefixbooleanrequired
  • targetPortinteger (int64)required
  • tlsModeenum: auto | custom | nonerequired
  • wwwPartnerstringrequired
    The counterpart hostname a www redirect would use (www.<host>, or the host without www.); empty when the domain cannot have one
  • wwwRedirectbooleanrequired
    A whole-host redirect from wwwPartner to this domain exists (both hostnames are routed and get certificates)
  • wwwRedirectForstringrequired
    For the redirect domain of a www redirect: the hostname it redirects to; empty otherwise
Errors
  • 401Unauthorized
  • 403Forbidden
  • 404Not Found
  • 409Conflictdomain_conflict
  • 422Unprocessable Entityproxy_unavailablecomponent_requiredtarget_port_required
  • 500Internal Server Error

Errors are RFC 9457 application/problem+json with a stable code field.

POST/services/{serviceId}/domains/generate

Generate a domain

Creates <service>-<env>-<project>.<base> where base is the server's base domain, else <public-ip>.sslip.io. 422 no_base_domain when neither exists. Compose stacks need component; the hostname is <service>-<component>-<env>-<project>.<base>.

ParameterInTypeDescription
serviceIdrequiredpathstring
componentquerystringCompose stacks only (required for them): the component the generated domain routes to
Response 201 · Created · application/json
DomainView
  • addPrefixstringrequired
  • authUsersstring[] | nullrequired
  • DomainCertrequired
  • certificateIdstringrequired
  • componentstringrequired
  • DomainDNSrequired
  • dnsCheckedAtstring (date-time) | nullrequired
  • dnsOkboolean | nullrequired
  • dnsResolverstringrequired
    The lego code of the DNS-01 provider that obtains the certificate; empty = HTTP-01 or a covering wildcard certificate
  • generatedbooleanrequired
  • hostnamestringrequired
  • idstringrequired
  • kindenum: serve | redirectrequired
  • RouteOptionsrequired
  • pathPrefixstringrequired
  • preservePathbooleanrequired
  • redirectCodeenum: 301 | 302 | 307 | 308required
  • redirectTostringrequired
  • statusstringrequired
  • statusMessagestringrequired
  • stripPrefixbooleanrequired
  • targetPortinteger (int64)required
  • tlsModeenum: auto | custom | nonerequired
  • wwwPartnerstringrequired
    The counterpart hostname a www redirect would use (www.<host>, or the host without www.); empty when the domain cannot have one
  • wwwRedirectbooleanrequired
    A whole-host redirect from wwwPartner to this domain exists (both hostnames are routed and get certificates)
  • wwwRedirectForstringrequired
    For the redirect domain of a www redirect: the hostname it redirects to; empty otherwise
Errors
  • 401Unauthorized
  • 403Forbidden
  • 404Not Found
  • 409Conflict
  • 422Unprocessable Entityno_base_domain
  • 500Internal Server Error

Errors are RFC 9457 application/problem+json with a stable code field.