Custom domains

Serve a site on your own domain — add it on the site's edit page or via the API, prove with one DNS record that the domain is yours, and the certificate is issued automatically.

Custom domains are an Enterprise feature. In the community edition the domain API returns 403.

Add a domain and prove you control it

Add the domain on the site's edit page (or with the API calls below). Until its DNS proves it belongs to this site, the claim is pending: recorded, and shown with the record to create, but not served and not issued a certificate. Either record suffices:

ProofRecord
TXT_sitebin-challenge.<domain> = sitebin-verify=<token>. The token is minted per site and per domain, and shown on the edit page when you add the domain (pending_domains[].txt_name / txt_value in the API).
CNAME<domain> → the site's own address — <id>.sitebin.app on the hosted service (pending_domains[].cname_target). That is also how the domain routes, so this route needs nothing extra.

A CNAME to the base domain (app.sitebin.io) is not proof: it cannot tell sites apart. Only the site's own address names the site.

Pending claims

The check runs when you add the domain, again when you press Check now on the edit page (a repeat POST of the same domain re-checks with the same token), and on every cleanup sweep — every 10 minutes on the hosted service — which attaches a claim on its own once the record appears, so you never have to come back. A claim that never verifies is dropped after 7 days; add the domain again to start over.

Each check that does not attach the domain says what it found, under the record on the edit page and as check_result in the API: no TXT record yet, and which of your nameservers said so; a TXT record with a different value (a missing sitebin-verify= prefix is the usual one); a CNAME that points somewhere else; or a lookup that failed. Check now can be pressed three times in a row, then once a minute per domain.

A pending claim reserves nothing. The site whose DNS carries its token gets the domain, whoever asked first — which is the point: without the proof, a stranger could claim docs.customer.com, wait for the customer to point DNS here, and be served on it with a valid certificate while the customer was told the domain was taken.

Route the domain

RecordNameValue
Ayour domainthe server's IP address
CNAMEyour domainthe site's own address, e.g. k3x9q2.sitebin.app

With the CNAME, routing and proof are the same record. With an A record, keep the TXT record permanently: verified domains are re-checked daily, and a domain whose proof has been definitively absent for three days is detached — no longer served — and goes back to pending with the same token, so restoring the record attaches it again. A lookup error (a timeout, a failing resolver) never detaches anything; only a definite "no such record" starts the three-day window, and the proof being seen again inside it closes the window.

How the record is checked

Sitebin asks your domain's own nameservers, not a caching resolver, so Check now sees a record as soon as your DNS provider publishes it — usually within a minute. It finds those nameservers through your domain's registry, so a domain registered minutes ago works too, even before resolvers have caught up with it.

Certificates

The certificate is issued automatically on the first HTTPS request — there is nothing to configure per domain. The internal tls-check endpoint issues certificates only for verified domains, which keeps strangers from triggering issuance for arbitrary hostnames (issuance-DoS).

Redirects and the generated address

Attach every domain you want to answer — example.at, example.de, www.example.at — and pick, for each, whether it serves the site or redirects to another of the site's domains. The redirect keeps the path and query (https://example.de/prices?x=1 → https://example.at/prices?x=1) and is answered by Sitebin itself, before anything is served, on the domain's own certificate — no redirect service, no extra DNS. Choose the status code:

CodeUse it for
301The default. Permanent: search engines move the ranking to the target.
302Temporary, for a move you may undo.
307Temporary, and the browser repeats a POST as a POST.
308Permanent, and the browser repeats a POST as a POST.

A permanent redirect is sent with Cache-Control: public, max-age=3600: browsers would otherwise remember it for ever, and a redirect you change later would keep sending your visitors to the old target. A temporary one is not cached.

The generated address — <id>.sitebin.app — keeps serving the site next to your domains until you change it. Once one of your domains serves the site, switch it off (visitors get “Site not found”) or have it redirect to your domain the same way. Off is the right choice when the site should not be found under sitebin.app at all; redirect keeps links that were already shared working.

The rules that keep this safe:

  • Only your own domains. A redirect points at another verified domain of the same site — never at a URL, never at someone else's site.
  • One hop. The target serves the site itself; a domain something redirects to cannot redirect in turn.
  • Never offline. Off needs a domain that serves the site. If that domain is ever detached — its DNS proof gone for three days — the generated address serves again on its own, and a redirect to a detached domain becomes a domain that serves, until the proof is back.
  • Removing a domain removes its rules. Domains that redirected to it serve the site again.

In the site's settings, each domain in the custom-domain card has two dropdowns: what it does, and the status code.

Container sites have the same rules per HTTP port: every port has a generated address of its own, each domain serves the port you point it at, and a port's generated address can be switched off once a domain serves that same port. See Containers.

Limits

The tier's custom_domains value is a per-site cap: it limits how many domains one site may carry, not how many your account may have in total. It is stamped onto each site, and changing tier restamps the sites you already own. On the hosted service the number comes from your plan. Pending claims count against this cap too, so claims cannot be sprayed. Names in your own zone do not count.

Self-hosted Enterprise instances have a second, separate ceiling: the license entitlement caps verified custom domains instance-wide. See Enterprise. Both are checked only when a domain is added — a domain already serving is never taken away by a cap.

API

Adding a domain answers 202 while the claim is pending and 200 once it is attached. Both return the site's settings document: attached domains under custom_domains, pending ones under pending_domains with the records to create. POST the same domain again to re-check; too many re-checks in a row answer 429.

# add a custom domain (Enterprise edition only; 403 in community)
# 202 = claimed, pending DNS proof; 200 = attached. POST again to re-check.
curl -X POST -H "X-Edit-Password: $PW" -H "Content-Type: application/json" \
     -d '{"domain":"docs.client.com"}' \
     https://app.sitebin.io/api/sites/$EDIT_ID/domains

# remove it (pending or attached)
curl -X DELETE -H "X-Edit-Password: $PW" \
     https://app.sitebin.io/api/sites/$EDIT_ID/domains/docs.client.com

# redirect an attached domain to another attached domain of the site
# (code 301 by default; 302, 307, 308). DELETE makes it serve again.
curl -X PUT -H "X-Edit-Password: $PW" -H "Content-Type: application/json" \
     -d '{"to":"client.com","code":301}' \
     https://app.sitebin.io/api/sites/$EDIT_ID/domains/www.client.com/redirect

# the generated address: {"mode":"serve"}, {"mode":"off"},
# or {"mode":"redirect","to":"client.com","code":301};
# on a container site, "endpoint":"api:8080" picks which port's address
curl -X PUT -H "X-Edit-Password: $PW" -H "Content-Type: application/json" \
     -d '{"mode":"off"}' \
     https://app.sitebin.io/api/sites/$EDIT_ID/view-address

The 202 body, abridged:

{
  "id": "k3x9q2",
  "view_url": "https://k3x9q2.sitebin.app",
  "custom_domains": [],
  "pending_domains": [
    {
      "domain": "docs.client.com",
      "txt_name": "_sitebin-challenge.docs.client.com",
      "txt_value": "sitebin-verify=<token>",
      "cname_target": "k3x9q2.sitebin.app",
      "requested_at": "2026-09-07T12:00:00Z",
      "checked_at": "2026-09-07T12:00:00Z",
      "check_result": "no TXT record at _sitebin-challenge.docs.client.com (answered by ns1.your-dns.example)"
    }
  ],
  "domain_recheck_minutes": 10,
  …
}

The same fields come back from GET /api/sites/$EDIT_ID, and the MCP add_domain tool returns pending_domains in its result instead of failing, with what the check found in warnings.

The settings document also carries the rules: domain_redirects ({"www.client.com": {"to": "client.com", "code": 301, "in_effect": true}}), view_address ({"mode": "off", "in_effect": true}) and primary_url, where the site is served first. in_effect is false while a rule cannot be followed, as described above. A container site's document adds addresses (each port's generated address and rule) and domain_targets (the port each domain serves). Over MCP, set_domain_redirect, set_view_address and set_domain_target do the same.

Zones: a whole domain, proven once

If your plan includes zones (Studio on the hosted service), you can add a domain you own as a zone on your account page and use every name under it without a DNS record per name. Create two records:

*.clients.example.com       CNAME  app.sitebin.io
_sitebin-zone.clients.example.com  TXT  "sitebin-zone=<token from your account page>"

Press Check now once the TXT record exists. From then on any name under the zone — acme.clients.example.com, and the zone itself — attaches to any of your sites the moment you add it. Names in your own zone do not count against your plan's custom-domain cap. Certificates are still issued per name, on the first visit, and only for names you have attached.

  • Only you. Every other account is refused a name in your zone, whatever DNS it can show.
  • No overlaps. A zone cannot overlap another account's zone, or one of your own — example.com and clients.example.com cannot both be zones.
  • First proof wins. An unproven zone reserves nothing; if two accounts try, the one whose TXT record appears first holds it. Unproven zones are dropped after seven days.
  • Nothing is taken away. If another account already has a verified domain inside your zone, the zone stays pending and your account page names that domain; it verifies once the domain is gone.
  • Keep the record. The TXT record is re-checked daily. If it stays gone for three days the zone is released, and names that relied on it need their own record (above) within another three days. Removing a zone on the account page works the same way: nothing is detached on the spot.
  • A new plan without zones keeps what you have but adds no new zones or names.
  • New names are limited to 50 per hour per account, to protect the shared certificate budget; the total is not limited.

Self-hosting: operator zones

If you own a zone and point all of it at your instance with a wildcard record (*.app.example.com), list it in SITEBIN_OPERATOR_DOMAINS. Its subdomains then attach to sites of the instance's operator — accounts with the admin tier that are also listed in SITEBIN_ADMIN_ACCOUNTS — at once and without a DNS record, and are refused to every other account. It is the same as a zone above, configured at startup instead of claimed, and no account can claim a zone that overlaps it. Without either, a wildcard pointed at a shared instance leaves every name under it waiting for whoever claims it first.

Self-hosting: SITEBIN_DOMAIN_VERIFICATION

dns (the default) attaches a domain only once the TXT record with the site's token or a CNAME at the site's own address is visible. off attaches on the owner's word alone, with a warning at startup — only for an instance whose every account holder you trust, such as a team's own install. On a path-only instance (SITEBIN_VIEW_ACCESS=path) there is no per-site view host, so cname_target is absent and only the TXT route exists. Domains attached before verification existed have no token to check against and are left alone.