Custom domains for SaaS
Let your customers bring their own domain
Give every workspace in your SaaS its own hostname. One API call returns the DNS records your customer adds; we handle verification, HTTPS and routing each request to the right workspace.
Read the quickstart: about fifteen minutes from sign-up to a customer's hostname on HTTPS, once its DNS records are in place.
POST /v1/domains HTTP/1.1
Host: edge.customdomainapi.com
Authorization: Bearer cd_…
Idempotency-Key: signup-ws_8f3a1c
Content-Type: application/json
{"hostname": "forms.acme.com",
"reference": "ws_8f3a1c"}HTTP/1.1 201 Created
Content-Type: application/json
{
"id": "6f1c2d3e-4b5a-4c6d-8e9f-0a1b2c3d4e5f",
"hostname": "forms.acme.com",
"reference": "ws_8f3a1c",
"status": "pending_dns",
"dns_records": [
{"type": "TXT",
"name": "_custom-domain-challenge.forms.acme.com",
"value": "custom-domain-verify=…",
"purpose": "ownership"},
{"type": "CNAME",
"name": "forms.acme.com",
"value": "edge.customdomainapi.com",
"purpose": "routing"}
],
"checks": [ … four checks, all "pending" ]
}GET /responses HTTP/1.1
Host: forms.acme.com
X-Forwarded-Proto: https
X-Custom-Domain-Assertion: v1.app_….eyJhcHAi….Lq3x…
# the payload, once your origin has verified it
{"app": "…", "dom": "6f1c2d3e-…",
"ref": "ws_8f3a1c", "host": "forms.acme.com",
"iat": 1791122400, "exp": 1791122460}How it works
Four checks, then HTTPS
Ownership, routing, certificate and origin are checked continuously. A hostname goes live only when all four pass, and stops the moment one fails. Certificates come from Let's Encrypt on demand, only for verified hostnames, and renew without you noticing.
| When | What happens | ownership | routing | certificate | origin | Domain |
|---|---|---|---|---|---|---|
| 4 Oct 14:00 | Your backend calls POST /v1/domains and shows your customer the two records. | Pending | Pending | Pending | Pending | Waiting for DNSpending_dns |
| 14:01 | First check. The customer hasn't added the records yet; retried in 2 minutes, then 5. | Failingtxt_ | Failingcname_ | Pending | Pending | Waiting for DNSpending_dns |
| 14:08 | The TXT and CNAME are found. The edge obtains a certificate from Let's Encrypt. | Passing | Passing | Pending | Pending | Provisioningprovisioning |
| 14:08 | Through the edge, your origin answers /.well-known/custom-domain-workspace with the workspace from the assertion. | Passing | Passing | Passing | Passing | Readyready |
| 9 Oct 09:12 | The customer changes the CNAME to another host. Serving stops at once. | Passing | Failingcname_ | Passing | Passing | Needs attentionattention_required |
| 09:40 | The customer points the CNAME back at the edge, and your backend asks for a re-check with POST /v1/domains/{id}/checks. Serving resumes. | Passing | Passing | Passing | Passing | Readyready |
Failing checks are retried after 1, 2, 5, 15 and 30 minutes, then hourly; passing ones every 6 hours. After your customer fixes a record, ask for an early re-check with POST /v1/domains/{id}/checks. A failing check carries a stable error_code and a message in plain words to show your customer.
Signed webhooks tell you when a domain is ready, needs attention, recovers or is deleted, with retries and replay. Every status and check, and the webhooks.
The integration rule
Select the workspace from the assertion, never from Host
In multi-tenant SaaS, one wrong route means one customer seeing another's data. A hostname alone doesn't prove which workspace may be served, so the edge says so in a signature your backend checks.
| Header | Set by | Selects the workspace |
|---|---|---|
| Host | The edge, to the customer's hostname | No: anyone can ask for any hostname |
| X-Forwarded-Host | The edge, replacing any value the visitor sent | No: for absolute URLs and redirects |
| X-Custom-Domain-Assertion | The edge, signed with your application's key, valid for 60 seconds. Copies sent by visitors are removed. | Yes, once verified: ref is the workspace |
app.add_middleware(
CustomDomainMiddleware,
keys={"app_…": os.environ["CUSTOM_DOMAIN_ASSERTION_SECRET"]},
application_id=os.environ["CUSTOM_DOMAIN_APPLICATION_ID"],
workspace_lookup=load_workspace, # reference -> workspace, or None
)
@app.get("/")
def home(request):
workspace = load_workspace(request.state.custom_domain.reference)Not Python? Node.js, and the algorithm for any language: HMAC-SHA256, seven steps.
AI assistants
Manage your domains from Claude, Cursor or any MCP client
The MCP server gives an assistant the v1 API as tools, so you can ask it to register a customer's hostname, explain the records to add, or find out why a domain isn't live yet. It acts as one application, with that application's API key.
uvx commandclaude mcp add custom-domain \
--env CUSTOM_DOMAIN_API_URL=https://edge.customdomainapi.com \
--env CUSTOM_DOMAIN_API_KEY=cd_... -- uvx custom-domain-mcp- Tools
- Seven: register a hostname, get or list domains, write the DNS instructions for your customer, recheck, delete, and list webhooks
- Access
- Your application's API key only, never an operator token: it can do what your backend can
- Left out
- Creating or rotating webhooks, because their signing secret would end up in the conversation
- Published
- On PyPI as custom-domain-mcp, and on the official MCP Registry as io.github.sireto/custom-domain
Hosted or self-hosted
The same open-source service, wherever it runs
Hosted, we run the edge, its certificates and its upgrades, and you manage it in the portal. Self-hosted, it is free and open source under Apache 2.0, on your own server or cloud. Your integration (the API, the SDK and the middleware) doesn't change between them, and with your own edge hostname moving is repointing that one name.
Each option below creates one server with fixed addresses, installs the latest release, and ends with the operator portal open at your edge's own name. Your cloud provider bills you directly.
- AzureDeploy to Azure buttonDeploy on Azure
- AWSCloudFormation stackOpen guide on AWS
- Google CloudGuided Cloud Shell scriptOpen in Cloud Shell on Google Cloud
- Hetzner CloudPaste the cloud-initOpen guide on Hetzner Cloud
- DigitalOceanPaste the user dataOpen guide on DigitalOcean
Or any Ubuntu or Debian server, with one install script: Self-hosting.
Everywhere: the v1 REST API with its OpenAPI document, the Python SDK with ASGI middleware, an MCP server for AI assistants, and signed webhooks. Self-hosted, the operator portal and the custom-domain command manage the rest.
Pricing
Priced by customer domains and bandwidth
Each plan includes a number of domains and a monthly bandwidth allowance, and the table says what happens above them: Free is never billed, paid plans are. Prices in US dollars, excluding VAT; yearly billing gives two months free.
| Free25 customer domains on our shared edge in the EU | Starter500 customer domains on your own edge in the EU | Growth5,000 customer domains on your own edge in the EU, backed up daily | Scale25,000 customer domains on your own edge, in the EU or the USA | |
|---|---|---|---|---|
| Price | $0 / month | $29 / month | $119 / month | $349 / month |
| Customer domains included | 25 | 500 | 5,000 | 25,000 |
| Edge | Shared edge | Dedicated edge | Dedicated edge | Dedicated edge |
| Domains above the allowance | Upgrade for more | $0.10 a month each, up to 2,000 | $0.05 a month each, up to 20,000 | More? Talk to us |
| Bandwidth included | 100 GB a month | 1 TB a month | 10 TB a month | 20 TB a month |
| Bandwidth above the allowance | Not billed: slowed to 60 requests a minute until the month ends | $0.05 per GB | $0.05 per GB | $0.05 per GB |
| Customers point their CNAME at | edge.customdomainapi.com | Your own edge hostname | Your own edge hostname | Your own edge hostname |
| Where the edge runs | The EU | Falkenstein or Helsinki (EU) | Falkenstein or Helsinki (EU) | Falkenstein or Helsinki (EU), or Ashburn (USA) |
| Fixed addresses | Not included | Fixed IPv4 and IPv6 | Fixed IPv4 and IPv6 | Fixed IPv4 and IPv6 |
| Upgrades and monitoring | Shared edge | Managed by us | Managed by us | Managed by us |
| Backups of the edge's database | Not included | Download your own from the portal | Daily, kept 30 days | Daily, kept 30 days |
| Proxied requests | Up to 600 a minute and 100 a second | No limit of ours | No limit of ours | No limit of ours |
| Support | Community support on GitHub | Email support | Email support | Email support |
| Choose a plan | Start free | Order Starter | Order Growth | Order Scale |
Free
$0 / month
25 customer domains on our shared edge in the EU
- Customer domains included
- 25
- Domains above the allowance
- Upgrade for more
- Bandwidth included
- 100 GB a month
- Bandwidth above the allowance
- Not billed: slowed to 60 requests a minute until the month ends
- Where the edge runs
- The EU
Start freeEverything in Free
- Edge
- Shared edge
- Customers point their CNAME at
edge.customdomainapi.com- Fixed addresses
- Not included
- Upgrades and monitoring
- Shared edge
- Backups of the edge's database
- Not included
- Proxied requests
- Up to 600 a minute and 100 a second
- Support
- Community support on GitHub
Starter
$29 / month
500 customer domains on your own edge in the EU
- Customer domains included
- 500
- Domains above the allowance
- $0.10 a month each, up to 2,000
- Bandwidth included
- 1 TB a month
- Bandwidth above the allowance
- $0.05 per GB
- Where the edge runs
- Falkenstein or Helsinki (EU)
Order StarterEverything in Starter
- Edge
- Dedicated edge
- Customers point their CNAME at
- Your own edge hostname
- Fixed addresses
- Fixed IPv4 and IPv6
- Upgrades and monitoring
- Managed by us
- Backups of the edge's database
- Download your own from the portal
- Proxied requests
- No limit of ours
- Support
- Email support
Growth
$119 / month
5,000 customer domains on your own edge in the EU, backed up daily
- Customer domains included
- 5,000
- Domains above the allowance
- $0.05 a month each, up to 20,000
- Bandwidth included
- 10 TB a month
- Bandwidth above the allowance
- $0.05 per GB
- Where the edge runs
- Falkenstein or Helsinki (EU)
Order GrowthEverything in Growth
- Edge
- Dedicated edge
- Customers point their CNAME at
- Your own edge hostname
- Fixed addresses
- Fixed IPv4 and IPv6
- Upgrades and monitoring
- Managed by us
- Backups of the edge's database
- Daily, kept 30 days
- Proxied requests
- No limit of ours
- Support
- Email support
Scale
$349 / month
25,000 customer domains on your own edge, in the EU or the USA
- Customer domains included
- 25,000
- Domains above the allowance
- More? Talk to us
- Bandwidth included
- 20 TB a month
- Bandwidth above the allowance
- $0.05 per GB
- Where the edge runs
- Falkenstein or Helsinki (EU), or Ashburn (USA)
Order ScaleEverything in Scale
- Edge
- Dedicated edge
- Customers point their CNAME at
- Your own edge hostname
- Fixed addresses
- Fixed IPv4 and IPv6
- Upgrades and monitoring
- Managed by us
- Backups of the edge's database
- Daily, kept 30 days
- Proxied requests
- No limit of ours
- Support
- Email support
Ordering a dedicated edge: sign in, choose the plan, monthly or yearly billing, the location and your edge's hostname, and pay with Stripe. We set up the server; once your edge hostname points at its fixed addresses, it is ready within minutes. Cancel any time: the edge runs to the end of the period you paid for, then keeps serving for 14 days while you download a backup and move your customers' hostnames.
- Second edge Coming soon
- A second edge on any paid plan, for failover or a second region. Tell us if you need it.
- Enterprise
- Managed in your own cloud account, several regions, an SLA. Talk to us.
FAQ
Questions about custom domains for SaaS
How do I let my SaaS customers use their own domain?
Register the customer's hostname with one API call (POST /v1/domains) and show them the two DNS records it returns: a TXT record that proves they own it and a CNAME to the edge. The edge verifies both, issues the HTTPS certificate and forwards every request to your app with a signed assertion naming the workspace. The quickstart takes about fifteen minutes.
What is an edge?
The server your customers' hostnames point at. It holds their certificates, terminates HTTPS and forwards each request to your app with a signed assertion of the workspace it belongs to.
How does my app know which workspace a request is for?
Every forwarded request carries an X-Custom-Domain-Assertion header, signed with your application's key, naming your application, the domain and the workspace. Your backend verifies it and selects the workspace from it, never from the Host header. See verifying requests.
Which hostnames are supported?
Exact customer subdomains, such as forms.acme.com. Apex domains (acme.com) and wildcards are not supported yet.
Does my backend have to be in Python?
No. Any language can use the REST API and verify the assertion: the API reference and verification include Node.js examples and the exact algorithm. The Python SDK and middleware make it a few lines in FastAPI or Starlette.
Can an AI coding agent do the integration?
Yes. Every docs page is also plain Markdown (add .md to its address), /llms.txt lists them, and For AI coding agents is a complete brief with the contracts, the invariants and a checklist to hand to your assistant. The MCP server (uvx custom-domain-mcp) lets the assistant register, inspect and recheck your domains with your application's API key.
What is the difference between Free and the paid plans?
Free runs your application on an edge shared with others, under edge.customdomainapi.com, for up to 25 domains, with up to 600 proxied requests a minute and 100 a second across your hostnames, and one API key at 60 requests a minute, and 100 GB of bandwidth a month. Every paid plan gives you a server of your own, under your own edge hostname, with more domains, more bandwidth (1 TB, 10 TB or 20 TB a month) and no request limit of ours.
Where do the edges run?
On Hetzner Cloud. A paid edge runs in the location you choose when you order it: Falkenstein (Germany) or Helsinki (Finland) on every paid plan, or Ashburn (Virginia, USA) on the Scale plan only. The shared Free edge runs in the European Union. Self-hosted, it runs wherever you deploy it.
What counts as a domain, and what happens at the limit?
Every customer hostname you have registered and not deleted, including those still waiting for their DNS records. Deleted ones don't count. You are warned as you approach the allowance; on Starter each extra domain costs $0.10 a month and on Growth $0.05, and on Free new ones wait until you upgrade. Domains that already work are never switched off.
How is bandwidth counted?
As the response bodies the edge sends to visitors; request bodies and headers don't count, and 1 GB is 1,000,000,000 bytes. On a paid edge, all its applications share the plan's allowance, and what is above it is billed at $0.05 per whole GB. How days, billing periods and yearly plans are counted: Bandwidth in the API reference.
What happens when a Free application uses more than 100 GB in a month?
We email your account at 80% and at 100% of the allowance, with a link to order a dedicated edge. Above 100 GB in a calendar month (UTC), your application's proxied requests are limited to 60 a minute and 5 a second, instead of 600 and 100, until the month ends; requests over the limit get 429 with Retry-After. Nothing is billed, deleted or switched off, and the normal limits come back on the first of the next month (UTC).
Can I move between hosted and self-hosted?
It is the same open-source service everywhere, so your integration (the API, the SDK and the middleware) does not change. Your customers' CNAME records name the edge they point at: with your own edge hostname (a paid plan or self-hosted) moving is repointing that one name, while leaving the shared Free edge means they update their CNAME.
Get started
Give your customers their own domain
Start on Free in minutes, without a card, or order a dedicated edge of your own in the portal, from $29 a month. Self-hosting is available today too.