curl --request POST \
--url https://app.base44.com/api/apps/{app_id}/custom-domains \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"domain": "example.com"
}
'import requests
url = "https://app.base44.com/api/apps/{app_id}/custom-domains"
payload = { "domain": "example.com" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({domain: 'example.com'})
};
fetch('https://app.base44.com/api/apps/{app_id}/custom-domains', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.base44.com/api/apps/{app_id}/custom-domains",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'domain' => 'example.com'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.base44.com/api/apps/{app_id}/custom-domains"
payload := strings.NewReader("{\n \"domain\": \"example.com\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.base44.com/api/apps/{app_id}/custom-domains")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"domain\": \"example.com\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.base44.com/api/apps/{app_id}/custom-domains")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"domain\": \"example.com\"\n}"
response = http.request(request)
puts response.read_body{
"id": "68b1c0d4e7b91d003c45a1f7",
"domain": "example.com",
"app_id": "6820f3a4e7b91d003c45a1f2",
"disabled": false,
"last_status_check": "2026-08-15T09:10:00",
"last_status_payload": {
"verificationStatus": "verified"
},
"redirect_target_domain": "www.example.com",
"provider_id": "wix"
}{
"detail": {
"message": "This domain is registered to another workspace. Add the TXT record below to prove you control it, then try again.",
"verification": {
"type": "TXT",
"name": "_b44-verify.example.com",
"value": "base44-domain-claim=3kQ9vL2mP7xR8tN4wZ1yB6cF5hJ0dS2aE9gU7iO3rT"
}
}
}Add custom domain
Attaches a domain you own to the app. Nothing serves on it until you link it with Link custom domain.
Adding a domain and linking it are separate steps. DNS propagation happens outside Base44, so a linked domain can sit unverified for as long as its nameservers take. The domain’s DNS has to point at Base44 before verification can pass, and the Base44 editor’s domain settings show the records to set.
If a domain was released by another workspace, the call is rejected with a TXT record to publish. Publish it at the domain’s DNS provider and send the same request again to prove you control the domain. A domain your own workspace released is re-added straight away.
Some Enterprise workspaces serve all their apps under one app domain, such as apps.example.com. A host at or under another workspace’s app domain is rejected outright. That workspace has already proved it controls the zone, so no TXT record is offered. Hosts under your own workspace’s app domain are rejected too while it’s connected, because it gives each app its address there.
Don’t retry a request that timed out without checking first. If the domain was added, a second request is rejected as already attached, so look for it in List custom domains before you send the request again.
Adding domains is limited to 30 requests an hour per caller. Some workspaces have a different limit.
curl --request POST \
--url https://app.base44.com/api/apps/{app_id}/custom-domains \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"domain": "example.com"
}
'import requests
url = "https://app.base44.com/api/apps/{app_id}/custom-domains"
payload = { "domain": "example.com" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({domain: 'example.com'})
};
fetch('https://app.base44.com/api/apps/{app_id}/custom-domains', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.base44.com/api/apps/{app_id}/custom-domains",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'domain' => 'example.com'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.base44.com/api/apps/{app_id}/custom-domains"
payload := strings.NewReader("{\n \"domain\": \"example.com\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.base44.com/api/apps/{app_id}/custom-domains")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"domain\": \"example.com\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.base44.com/api/apps/{app_id}/custom-domains")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"domain\": \"example.com\"\n}"
response = http.request(request)
puts response.read_body{
"id": "68b1c0d4e7b91d003c45a1f7",
"domain": "example.com",
"app_id": "6820f3a4e7b91d003c45a1f2",
"disabled": false,
"last_status_check": "2026-08-15T09:10:00",
"last_status_payload": {
"verificationStatus": "verified"
},
"redirect_target_domain": "www.example.com",
"provider_id": "wix"
}{
"detail": {
"message": "This domain is registered to another workspace. Add the TXT record below to prove you control it, then try again.",
"verification": {
"type": "TXT",
"name": "_b44-verify.example.com",
"value": "base44-domain-claim=3kQ9vL2mP7xR8tN4wZ1yB6cF5hJ0dS2aE9gU7iO3rT"
}
}
}Authorizations
Personal access token, sent as Authorization: Bearer <token>. Each operation lists the permission it needs: read_only or full_access. A full-access token can call every operation. A read-only token can call only the operations that list read_only; anything else returns 403 read_only_credential_route_not_allowed.
Path Parameters
ID of the app the domain belongs to.
Body
The domain to add. A scheme, a trailing slash, and a leading www. are removed, and it's stored in lower case.
"example.com"
Response
The domain, attached to the app but not yet linked.
A custom domain attached to an app.
ID of the domain. Pass it as domain_id to the other domain endpoints.
"68b1c0d4e7b91d003c45a1f7"
The domain name, normalized to lower case with any scheme, trailing slash and leading www. removed.
"example.com"
ID of the app the domain serves.
"6820f3a4e7b91d003c45a1f2"
Whether Base44 has stopped serving the domain. A disabled domain stays attached to the app and keeps its DNS, and serves a placeholder page instead of the app.
false
When Get custom domain status last ran for this domain, as a UTC timestamp in ISO 8601 format, or null if it never has.
"2026-08-15T09:10:00"
The provider's last report on the domain, or null until a status check has run.
Show child attributes
Show child attributes
The domain that visitors to this one are sent to with a 301 redirect. The value is null when this domain serves the app itself.
"www.example.com"
Names the registrar when the domain was bought through Base44's Wix flow. The value is null otherwise, which covers both a domain you own elsewhere and one bought through the Entri flow, so this isn't a purchased-versus-external signal.
"wix"
Was this page helpful?