Add Application Domain
curl --request POST \
--url https://api.vertracloud.app/v1/apps/{id}/domains \
--header 'Content-Type: application/json' \
--data '
{
"hostname": "<string>",
"www": true,
"port": 123,
"path_prefix": "<string>",
"redirect_to": "<string>",
"redirect_status": 123
}
'import requests
url = "https://api.vertracloud.app/v1/apps/{id}/domains"
payload = {
"hostname": "<string>",
"www": True,
"port": 123,
"path_prefix": "<string>",
"redirect_to": "<string>",
"redirect_status": 123
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
hostname: '<string>',
www: true,
port: 123,
path_prefix: '<string>',
redirect_to: '<string>',
redirect_status: 123
})
};
fetch('https://api.vertracloud.app/v1/apps/{id}/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://api.vertracloud.app/v1/apps/{id}/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([
'hostname' => '<string>',
'www' => true,
'port' => 123,
'path_prefix' => '<string>',
'redirect_to' => '<string>',
'redirect_status' => 123
]),
CURLOPT_HTTPHEADER => [
"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://api.vertracloud.app/v1/apps/{id}/domains"
payload := strings.NewReader("{\n \"hostname\": \"<string>\",\n \"www\": true,\n \"port\": 123,\n \"path_prefix\": \"<string>\",\n \"redirect_to\": \"<string>\",\n \"redirect_status\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
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://api.vertracloud.app/v1/apps/{id}/domains")
.header("Content-Type", "application/json")
.body("{\n \"hostname\": \"<string>\",\n \"www\": true,\n \"port\": 123,\n \"path_prefix\": \"<string>\",\n \"redirect_to\": \"<string>\",\n \"redirect_status\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.vertracloud.app/v1/apps/{id}/domains")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"hostname\": \"<string>\",\n \"www\": true,\n \"port\": 123,\n \"path_prefix\": \"<string>\",\n \"redirect_to\": \"<string>\",\n \"redirect_status\": 123\n}"
response = http.request(request)
puts response.read_body{
"response": [
{
"id": "7b8f5e0a-3c1d-4e52-9a47-0d6e2c1f9b34",
"hostname": "example.com",
"apex": true,
"companion_of": null,
"redirect_to": null,
"redirect_status": 308,
"path_prefix": null,
"port": 80,
"status": "pending",
"ssl_status": "pending",
"created_at": "2026-10-21T14:02:11.000Z"
},
{
"id": "c2a9d4f1-86b0-4a37-b5e8-1f3d7a9c0e62",
"hostname": "www.example.com",
"apex": false,
"companion_of": "7b8f5e0a-3c1d-4e52-9a47-0d6e2c1f9b34",
"redirect_to": "7b8f5e0a-3c1d-4e52-9a47-0d6e2c1f9b34",
"redirect_status": 308,
"path_prefix": null,
"port": 80,
"status": "pending",
"ssl_status": "pending",
"created_at": "2026-10-21T14:02:11.000Z"
}
]
}
Domains
Add Application Domain
Adds a custom domain to a published application, without redeploying it, with optional www, port, path prefix and redirect.
POST
/
v1
/
apps
/
{id}
/
domains
Add Application Domain
curl --request POST \
--url https://api.vertracloud.app/v1/apps/{id}/domains \
--header 'Content-Type: application/json' \
--data '
{
"hostname": "<string>",
"www": true,
"port": 123,
"path_prefix": "<string>",
"redirect_to": "<string>",
"redirect_status": 123
}
'import requests
url = "https://api.vertracloud.app/v1/apps/{id}/domains"
payload = {
"hostname": "<string>",
"www": True,
"port": 123,
"path_prefix": "<string>",
"redirect_to": "<string>",
"redirect_status": 123
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
hostname: '<string>',
www: true,
port: 123,
path_prefix: '<string>',
redirect_to: '<string>',
redirect_status: 123
})
};
fetch('https://api.vertracloud.app/v1/apps/{id}/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://api.vertracloud.app/v1/apps/{id}/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([
'hostname' => '<string>',
'www' => true,
'port' => 123,
'path_prefix' => '<string>',
'redirect_to' => '<string>',
'redirect_status' => 123
]),
CURLOPT_HTTPHEADER => [
"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://api.vertracloud.app/v1/apps/{id}/domains"
payload := strings.NewReader("{\n \"hostname\": \"<string>\",\n \"www\": true,\n \"port\": 123,\n \"path_prefix\": \"<string>\",\n \"redirect_to\": \"<string>\",\n \"redirect_status\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
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://api.vertracloud.app/v1/apps/{id}/domains")
.header("Content-Type", "application/json")
.body("{\n \"hostname\": \"<string>\",\n \"www\": true,\n \"port\": 123,\n \"path_prefix\": \"<string>\",\n \"redirect_to\": \"<string>\",\n \"redirect_status\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.vertracloud.app/v1/apps/{id}/domains")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"hostname\": \"<string>\",\n \"www\": true,\n \"port\": 123,\n \"path_prefix\": \"<string>\",\n \"redirect_to\": \"<string>\",\n \"redirect_status\": 123\n}"
response = http.request(request)
puts response.read_body{
"response": [
{
"id": "7b8f5e0a-3c1d-4e52-9a47-0d6e2c1f9b34",
"hostname": "example.com",
"apex": true,
"companion_of": null,
"redirect_to": null,
"redirect_status": 308,
"path_prefix": null,
"port": 80,
"status": "pending",
"ssl_status": "pending",
"created_at": "2026-10-21T14:02:11.000Z"
},
{
"id": "c2a9d4f1-86b0-4a37-b5e8-1f3d7a9c0e62",
"hostname": "www.example.com",
"apex": false,
"companion_of": "7b8f5e0a-3c1d-4e52-9a47-0d6e2c1f9b34",
"redirect_to": "7b8f5e0a-3c1d-4e52-9a47-0d6e2c1f9b34",
"redirect_status": 308,
"path_prefix": null,
"port": 80,
"status": "pending",
"ssl_status": "pending",
"created_at": "2026-10-21T14:02:11.000Z"
}
]
}
Adds a custom domain to the application and answers
201 Created with the new domain. The application is not
restarted or redeployed. The domain starts as pending until its DNS records resolve; read them with
Domain DNS Records. Requires the apps:write scope when called with an API key.
By default the call also creates the automatic companion: www.example.com when you add example.com, or
example.com when you add www.example.com. The companion redirects to the domain you added with a 308, and it
does not count toward your plan’s domain limit. For any other subdomain (app.example.com) there is no companion.
string
required
The application ID. The application must be published on the web (it needs a
*.vertraweb.app subdomain).string
If the application belongs to a workspace, the workspace’s ID. Required for workspace members acting on an application they don’t own.
string
required
The domain to add (e.g.,
example.com or api.example.com), between 1 and 253 characters. It is stored in
lowercase. Send the bare hostname: a scheme, a path or a port is rejected with INVALID_CUSTOM_DOMAIN_FORMAT.
Domains that belong to Vertra Cloud are rejected with FORBIDDEN_DOMAIN.boolean
default:"true"
Whether to create the automatic
www (or root) companion. Send false to add only the hostname you sent. It has no
effect on subdomains other than www, and it is ignored when redirect_to is set.integer
default:"80"
The port of the application that serves this domain, an integer from
1 to 65535. An application can serve its
domains on up to 5 different ports besides 80; a sixth answers DOMAIN_PORT_LIMIT_REACHED. A domain that only
redirects does not use a port.string
A literal path added in front of every request the domain forwards to the application: with
/api, a request for
https://api.example.com/users reaches the application as /api/users. It must start with /, must not end with
/, is up to 128 characters, and may only contain letters, digits, ., _, ~ and - between slashes
(e.g., /api or /v1/api). Recommended for APIs: a site must be built to run under that path, including its assets
and its own redirects.string
The ID of another domain of the same application. The new domain then redirects every request to that domain,
keeping the path and the query string, instead of serving the application. The target must not redirect itself.
integer
default:"308"
The HTTP status of the redirect:
301, 302, 307 or 308. Only used together with redirect_to. 301 and 308
are permanent; 302 and 307 are temporary. 307 and 308 keep the request method; 301 and 302 may turn a
POST into a GET.Response
APIApplicationDomain[]
The domain you added first, followed by its companion when one was created. Each item has the fields described in
List Application Domains.
{
"response": [
{
"id": "7b8f5e0a-3c1d-4e52-9a47-0d6e2c1f9b34",
"hostname": "example.com",
"apex": true,
"companion_of": null,
"redirect_to": null,
"redirect_status": 308,
"path_prefix": null,
"port": 80,
"status": "pending",
"ssl_status": "pending",
"created_at": "2026-10-21T14:02:11.000Z"
},
{
"id": "c2a9d4f1-86b0-4a37-b5e8-1f3d7a9c0e62",
"hostname": "www.example.com",
"apex": false,
"companion_of": "7b8f5e0a-3c1d-4e52-9a47-0d6e2c1f9b34",
"redirect_to": "7b8f5e0a-3c1d-4e52-9a47-0d6e2c1f9b34",
"redirect_status": 308,
"path_prefix": null,
"port": 80,
"status": "pending",
"ssl_status": "pending",
"created_at": "2026-10-21T14:02:11.000Z"
}
]
}
Custom domains start at the Intermediary plan (1 domain per application) and grow with the Enterprise tier. The
automatic companion never counts. The limits per plan are in Plans and limits.
Unpublishing the application removes all of its domains. Removing a domain, by hand, by unpublishing or by the
automatic cleanup, never changes your DNS: delete its
CNAME and TXT records yourself, as explained in
Remove Application Domain.Error Responses
string
The error code indicating the reason for the failure. Possible values:
VALIDATION_ERROR: The path parameter or the body failed validation, for example an unknown field or a wrong type (400). The response also carriesmessageandpath.INVALID_CUSTOM_DOMAIN_FORMAT:hostnameis not a valid public hostname (400).FORBIDDEN_DOMAIN: The domain is reserved and cannot be added (400).INVALID_DOMAIN_PORT:portis not an integer from 1 to 65535 (400).INVALID_PATH_PREFIX:path_prefixdoes not follow the format above (400).INVALID_REDIRECT_TARGET:redirect_tois not a domain of this application, is notactive, or the target redirects itself (400).APP_NOT_PUBLISHED: The application is not published on the web; publish it first with Publish Application on the Web (400).UNAUTHORIZED: The request is not authenticated (401).USER_NOT_FOUND: The authenticated user could not be resolved (401).ACCESS_DENIED: The user does not have permission to act on the application (403).API_KEY_SCOPE_DENIED: The API key does not have theapps:writescope (403).PLAN_REQUIRED: The application’s owner has no active plan (403).PLAN_DOES_NOT_SUPPORT_CUSTOM_DOMAIN: The owner’s plan does not include custom domains (403).DOMAIN_LIMIT_REACHED: The application already has as many domains as the plan allows (403).details.limitcarries the limit.DOMAIN_PORT_LIMIT_REACHED: The application already serves domains on 5 ports besides80(403).details.limitcarries the limit.APP_NOT_FOUND: The specified application does not exist (404).DOMAIN_ALREADY_IN_USE: The hostname is already attached to an application (409).RATE_LIMIT_EXCEEDED: The route’s budget was reached (429).CUSTOM_DOMAIN_REGISTRATION_FAILED: The domain could not be registered right now; retry later (502).INTERNAL_SERVER_ERROR: Unexpected failure (500).
{
"code": "DOMAIN_LIMIT_REACHED",
"details": { "limit": 1 }
}
{
"code": "DOMAIN_ALREADY_IN_USE"
}
This route has its own budget of 10 requests per minute. See Rate Limiting.