Publish Application on the Web
curl --request POST \
--url https://api.vertracloud.app/v1/apps/{id}/network/publish \
--header 'Content-Type: application/json' \
--data '
{
"subdomain": "<string>"
}
'import requests
url = "https://api.vertracloud.app/v1/apps/{id}/network/publish"
payload = { "subdomain": "<string>" }
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({subdomain: '<string>'})
};
fetch('https://api.vertracloud.app/v1/apps/{id}/network/publish', 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}/network/publish",
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([
'subdomain' => '<string>'
]),
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}/network/publish"
payload := strings.NewReader("{\n \"subdomain\": \"<string>\"\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}/network/publish")
.header("Content-Type", "application/json")
.body("{\n \"subdomain\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.vertracloud.app/v1/apps/{id}/network/publish")
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 \"subdomain\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"response": {
"subdomain": "myapp.vertraweb.app",
"custom_domain": null,
"type": 2
}
}
{
"response": {
"subdomain": null,
"custom_domain": null,
"type": 1
}
}
Network
Publish Application on the Web
Turns web publishing on for an application created without it, and off again.
POST
/
v1
/
apps
/
{id}
/
network
/
publish
Publish Application on the Web
curl --request POST \
--url https://api.vertracloud.app/v1/apps/{id}/network/publish \
--header 'Content-Type: application/json' \
--data '
{
"subdomain": "<string>"
}
'import requests
url = "https://api.vertracloud.app/v1/apps/{id}/network/publish"
payload = { "subdomain": "<string>" }
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({subdomain: '<string>'})
};
fetch('https://api.vertracloud.app/v1/apps/{id}/network/publish', 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}/network/publish",
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([
'subdomain' => '<string>'
]),
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}/network/publish"
payload := strings.NewReader("{\n \"subdomain\": \"<string>\"\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}/network/publish")
.header("Content-Type", "application/json")
.body("{\n \"subdomain\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.vertracloud.app/v1/apps/{id}/network/publish")
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 \"subdomain\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"response": {
"subdomain": "myapp.vertraweb.app",
"custom_domain": null,
"type": 2
}
}
{
"response": {
"subdomain": null,
"custom_domain": null,
"type": 1
}
}
An application created without a
Takes no body. It releases the subdomain, detaches the custom domain and turns the application back
into a private one, then recreates the container. The response has the same shape, with
subdomain is private: it runs, but nothing routes to it from the
internet. These two routes turn web publishing on and off at any time, without recreating the
application.
Both routes recreate the container, so the application restarts. The routing configuration and the
exposed port only exist from container creation, so publishing state cannot change in place.
Expect a short downtime on each call.
Unpublishing releases the subdomain. It is not held for you — anyone can register it right
afterwards, and you may not get it back. The custom domain, if any, is detached as well (including
its CDN hostname), and reconnecting it later means publishing the DNS records again.
string
required
The application ID. Only the application’s owner can call this endpoint; workspace members are not accepted here yet.
string
The subdomain to publish under, without the
.vertraweb.app suffix (e.g., myapp). Between 3 and
50 characters, matching ^[a-z0-9][a-z0-9-]*[a-z0-9]$.Optional. Omit it and the platform picks a subdomain for you. Sending a value requires a plan
with custom subdomains; picking one at random does not.Response
object
Show Toggle object
Show Toggle object
string | null
The full subdomain now serving the application, or
null once publishing is off.string | null
The attached custom domain, or
null. Always null right after publishing — attach one with
Attach Custom Domain.integer
The application type after the change:
2 (website) when published, 1 (bot) when not.{
"response": {
"subdomain": "myapp.vertraweb.app",
"custom_domain": null,
"type": 2
}
}
{
"response": {
"subdomain": null,
"custom_domain": null,
"type": 1
}
}
Unpublish
DELETE https://api.vertracloud.app/v1/apps/{id}/network/publish
subdomain
and custom_domain set to null and type back to 1.
Memory floor
A published application needs at least 512 MB of RAM. Memory is never raised for you here: if the application has less, the request fails withMEMORY_BELOW_MINIMUM and the current and required
values come back in details. Raise it with
Update Application Config and try again.
Error Response (400)
{
"code": "MEMORY_BELOW_MINIMUM",
"details": {
"current_mb": 256,
"minimum_mb": 512
}
}
Error Responses
string
The error code indicating the reason for the failure. Possible values:
VALIDATION_ERROR: The path parameter or the body failed validation (400). This is what a malformedsubdomainreturns — 3 to 50 characters,^[a-z0-9][a-z0-9-]*[a-z0-9]$. The response also carriesmessageandpath.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).PLAN_DOES_NOT_SUPPORT_WEB_PUBLISH: The owner’s plan does not include web publishing (403).PLAN_DOES_NOT_SUPPORT_CUSTOM_SUBDOMAIN: Asubdomainwas sent but the owner’s plan does not allow choosing the name (403). Omit the field to get a random one.APP_NOT_FOUND: The specified application does not exist (404).APP_ALREADY_PUBLISHED: The application already has a subdomain or a custom domain (400).APP_NOT_PUBLISHED:DELETEon an application that is not published (400).MEMORY_BELOW_MINIMUM: The application has less RAM than a published application requires (400), withdetails: { current_mb, minimum_mb }.SUBDOMAIN_FORBIDDEN: The requested subdomain is on the platform’s reserved list (400).SUBDOMAIN_TAKEN: Another application already uses that subdomain (409).OPERATION_IN_PROGRESS: Another lifecycle operation on the same application is running; the container was not touched (409). Wait a few seconds and retry.INTERNAL_SERVER_ERROR: Unexpected failure (500).
string
A descriptive message providing additional details about the error.
{
"code": "PLAN_DOES_NOT_SUPPORT_WEB_PUBLISH"
}
{
"code": "APP_ALREADY_PUBLISHED"
}
{
"code": "SUBDOMAIN_TAKEN"
}
Two limits apply, and they are deliberately tight. Publishing and unpublishing recreate the
container, so they share the per-application lifecycle budget with
start, stop, restart and
dependency installs: 10 requests per minute for all of them together, per application — not
one budget per route. On top of that, a successful publish starts a 5-minute cooldown on the
application’s public address, shared with
Change Subdomain: publishing again inside that
window returns 429 SUBDOMAIN_CHANGE_RATE_LIMITED. Unpublishing and publishing under a new name
is an address change like any other and does not bypass it. Refusals do not consume the cooldown —
only a publish that actually happened does. See Rate Limiting.