Add a Custom Domain for Blob Storage
curl --request POST \
--url https://blob.vertracloud.app/v1/domains \
--header 'Content-Type: application/json' \
--data '
{
"hostname": "<string>",
"prefix": "<string>",
"web_content": true
}
'import requests
url = "https://blob.vertracloud.app/v1/domains"
payload = {
"hostname": "<string>",
"prefix": "<string>",
"web_content": True
}
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>', prefix: '<string>', web_content: true})
};
fetch('https://blob.vertracloud.app/v1/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://blob.vertracloud.app/v1/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>',
'prefix' => '<string>',
'web_content' => true
]),
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://blob.vertracloud.app/v1/domains"
payload := strings.NewReader("{\n \"hostname\": \"<string>\",\n \"prefix\": \"<string>\",\n \"web_content\": true\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://blob.vertracloud.app/v1/domains")
.header("Content-Type", "application/json")
.body("{\n \"hostname\": \"<string>\",\n \"prefix\": \"<string>\",\n \"web_content\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://blob.vertracloud.app/v1/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 \"prefix\": \"<string>\",\n \"web_content\": true\n}"
response = http.request(request)
puts response.read_body{
"response": {
"id": "9a8b7c6d-5e4f-4a3b-b2c1-d0e9f8a7b6c5",
"hostname": "cdn.example.com",
"status": "pending",
"ssl_status": "pending_validation",
"cname_target": "cname.vertracloud.dev",
"prefix": "images/",
"web_content": false,
"verification": { "type": "txt", "name": "_vertra-blob.cdn.example.com", "value": "3f9a1c7e2b5d4e8a9c0d6a1b2c3d4e5f" },
"created_at": "2026-10-14T12:00:00.000Z"
}
}
Custom domains
Add a Custom Domain for Blob Storage
Adds a subdomain that serves public blob files. Returns 201; create the CNAME and TXT records to activate it.
POST
/
v1
/
domains
Add a Custom Domain for Blob Storage
curl --request POST \
--url https://blob.vertracloud.app/v1/domains \
--header 'Content-Type: application/json' \
--data '
{
"hostname": "<string>",
"prefix": "<string>",
"web_content": true
}
'import requests
url = "https://blob.vertracloud.app/v1/domains"
payload = {
"hostname": "<string>",
"prefix": "<string>",
"web_content": True
}
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>', prefix: '<string>', web_content: true})
};
fetch('https://blob.vertracloud.app/v1/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://blob.vertracloud.app/v1/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>',
'prefix' => '<string>',
'web_content' => true
]),
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://blob.vertracloud.app/v1/domains"
payload := strings.NewReader("{\n \"hostname\": \"<string>\",\n \"prefix\": \"<string>\",\n \"web_content\": true\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://blob.vertracloud.app/v1/domains")
.header("Content-Type", "application/json")
.body("{\n \"hostname\": \"<string>\",\n \"prefix\": \"<string>\",\n \"web_content\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://blob.vertracloud.app/v1/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 \"prefix\": \"<string>\",\n \"web_content\": true\n}"
response = http.request(request)
puts response.read_body{
"response": {
"id": "9a8b7c6d-5e4f-4a3b-b2c1-d0e9f8a7b6c5",
"hostname": "cdn.example.com",
"status": "pending",
"ssl_status": "pending_validation",
"cname_target": "cname.vertracloud.dev",
"prefix": "images/",
"web_content": false,
"verification": { "type": "txt", "name": "_vertra-blob.cdn.example.com", "value": "3f9a1c7e2b5d4e8a9c0d6a1b2c3d4e5f" },
"created_at": "2026-10-14T12:00:00.000Z"
}
}
Adds a subdomain, such as
cdn.example.com, that serves the account’s public blob files. Create a CNAME record of that hostname pointing to cname_target and the TXT record from verification, then call Verify a custom domain. The number of domains depends on the plan: 0 below Enterprise-4, then 1, 3 and 10. Returns 201.
Scope: blob:write.
string
required
Subdomain only, without a scheme or path.
string
Serve only this folder on the domain. Omit it to serve the whole account.
boolean
Defaults to
false. When true, the domain serves .html, .htm, .xhtml, .svg and .xml files in the browser with their real type, sandboxed. See Custom domains.string
The workspace ID, when the blob belongs to a workspace. The call acts on the blob of the workspace owner and needs the
blob:read workspace permission (blob:manage on routes that change data). Leave it out to act on your own blob.Example request
curl -X POST "https://blob.vertracloud.app/v1/domains" \
-H "Authorization: Bearer $VERTRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"hostname":"cdn.example.com","prefix":"images/","web_content":false}'
Response
object
The domain, still
pending until the CNAME is found and the ownership TXT record is verified. It only becomes active after both. Responds with 201.Show properties
Show properties
string
Domain ID.
string
Subdomain only, for example
cdn.example.com.string
pending, active, failed or suspended. Only active serves files.string
Status of the TLS certificate for the hostname.
string
Point a CNAME record of
hostname to this value.string | null
When set, the domain serves only this folder.
boolean
true when the domain serves .html, .htm, .xhtml, .svg and .xml in the browser (sandboxed); false when those types always download.object | null
string
ISO 8601 creation time.
{
"response": {
"id": "9a8b7c6d-5e4f-4a3b-b2c1-d0e9f8a7b6c5",
"hostname": "cdn.example.com",
"status": "pending",
"ssl_status": "pending_validation",
"cname_target": "cname.vertracloud.dev",
"prefix": "images/",
"web_content": false,
"verification": { "type": "txt", "name": "_vertra-blob.cdn.example.com", "value": "3f9a1c7e2b5d4e8a9c0d6a1b2c3d4e5f" },
"created_at": "2026-10-14T12:00:00.000Z"
}
}
Error Responses
string
The error code indicating the reason for failure. Possible values:
AUTH_TOKEN_MISSING(401): NoAuthorizationheader was sent.API_KEY_INVALID(401): The API key doesn’t exist, was revoked, or its secret doesn’t match.API_KEY_SCOPE_DENIED(403): The key lacks the scope this route needs (blob:readorblob:write);details.requirednames it.WORKSPACE_PERMISSION_DENIED(403): Only withworkspace_id: you’re not a member, or your role lacks the blob permission.WORKSPACE_NOT_FOUND(404): Only withworkspace_id: no workspace with that ID, or you’re not a member.VALIDATION_ERROR(400): A parameter or body field failed validation.details.pathnames the field.INVALID_CUSTOM_DOMAIN_FORMAT(400): The hostname isn’t a valid subdomain.PLAN_RESTRICTED_FEATURE(403): The plan has no custom domains (below Enterprise-4).BLOB_NAMESPACE_SUSPENDED(403): The account’s blob storage is suspended, or its plan expired.BLOB_CUSTOM_DOMAIN_LIMIT(409): The plan includes custom domains and the limit is reached.BLOB_CUSTOM_DOMAIN_TAKEN(409): The hostname is already in use.
{
"code": "AUTH_TOKEN_MISSING"
}
{
"code": "API_KEY_SCOPE_DENIED"
}
{
"code": "WORKSPACE_NOT_FOUND"
}
{
"code": "VALIDATION_ERROR"
}
{
"code": "BLOB_CUSTOM_DOMAIN_LIMIT"
}