Services
Create a service, and list, attach, and remove its custom domains through the HTTP API.
Send a project token as a bearer token. If the token is scoped to an environment, use that environment's ID in the path.
BASE = https://api.cloud.simplifyd.com/v1/workspaces/{workspace}/projects/{project}/envs/{env}/svcsSet BASE using your workspace, project, and environment slugs. Set SIMPLIFYD_API_TOKEN to your project token and SERVICE to the service slug returned at creation.
| Method and path | What it does |
|---|---|
POST {BASE} | Creates a service |
GET {BASE}/{service}/ingress | Lists the service's hostnames and custom domains |
POST {BASE}/{service}/ingress | Attaches a custom domain to an HTTP service |
DELETE {BASE}/{service}/ingress/{hostname} | Removes a custom domain from an HTTP service |
POST {BASE}/{service}/site/domains | Attaches a custom domain to a static site |
Create a service
POST {BASE}
Content-Type: application/jsonCreates a service in the target environment. This example uses the nginx:alpine image. Deploy the container before it can serve traffic.
Body
| Field | Description |
|---|---|
type | Required service type. Use docker for a container. |
name | Display name. For a container, it defaults to the image name. |
docker_image_svc.image | Container image name. Supply this when creating a container. |
docker_image_svc.tag | Optional image tag. |
static_site_svc.name | Site name when type is static_site. The API also accepts the top-level name for a static site. |
curl --fail-with-body -X POST "$BASE" \
-H "Authorization: Bearer $SIMPLIFYD_API_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"name": "web", "type": "docker", "docker_image_svc": {"image": "nginx", "tag": "alpine"}}'const response = await fetch(`${BASE}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIMPLIFYD_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({"name": "web", "type": "docker", "docker_image_svc": {"image": "nginx", "tag": "alpine"}}),
});
const result = await response.json();
if (!response.ok) throw new Error(result.message ?? response.statusText);Response
201 Created.
{
"slug": "0192e7a0-55c1-7b3e-8f2d-4a1b6c9d0e15",
"name": "web",
"type": "docker",
"vcpus": 2,
"memory": 4096,
"replicas": 1,
"region": "ng-west-1"
}The response is trimmed. Use slug as {service} in subsequent requests. For VM-specific fields, see Create a VM.
List a service's custom domains
GET {BASE}/{service}/ingressReturns the service's ingress ports. Each HTTP port lists its platform hostname and every custom domain attached to it, with DNS and certificate status. A service with no ingress returns [].
curl --fail-with-body "$BASE/$SERVICE/ingress" \
-H "Authorization: Bearer $SIMPLIFYD_API_TOKEN"To print only the custom domain hostnames:
curl --fail-with-body -s "$BASE/$SERVICE/ingress" \
-H "Authorization: Bearer $SIMPLIFYD_API_TOKEN" \
| jq -r '.[].custom_fqdns[]?.fqdn'const response = await fetch(`${BASE}/${SERVICE}/ingress`, {
headers: { Authorization: `Bearer ${process.env.SIMPLIFYD_API_TOKEN}` },
});
const ports = await response.json();
if (!response.ok) throw new Error(ports.message ?? response.statusText);
const hostnames = ports.flatMap((port) => (port.custom_fqdns ?? []).map((d) => d.fqdn));Response
200 OK.
[
{
"slug": "0192e7a1-0c4f-7d2a-9b6e-3f8a1c5d7e20",
"protocol": "HTTP",
"port": 80,
"vanity_fqdn": "api-acme.simplifyd.app",
"custom_fqdns": [
{
"slug": "0192e7a1-2b7d-7e91-a4c3-6d0f2e8b9a31",
"fqdn": "app.example.com",
"verified": true,
"verified_at": "2026-11-03T14:22:09Z",
"cname": "z-0192e7a0-55c1-7b3e-8f2d-4a1b6c9d0e16.simplifyd.app",
"cert_status": "ready"
}
]
}
]| Field | Description |
|---|---|
slug | The ingress port's ID. |
protocol | HTTP, gRPC, TCP, or UDP. TCP and UDP ports have external_port and no hostnames. |
vanity_fqdn | The platform hostname for the port, if it has one. |
custom_fqdns[].fqdn | The custom domain. Pass this, or its slug, to remove it. |
custom_fqdns[].verified | Whether the domain's DNS record has been found. |
custom_fqdns[].cname | The target to point the domain's CNAME record at. |
custom_fqdns[].cert_status | pending, issuing, ready, or failed. cert_status_message explains a failure. |
custom_fqdns[].dns_zone | Set when the domain is in a DNS zone the workspace hosts here, so its records are managed for you. |
Optional fields with no value are left out, and custom_fqdns is absent on a port with no custom domains.
Attach a custom domain to an HTTP service
POST {BASE}/{service}/ingress
Content-Type: application/jsonRoutes a hostname to an HTTP port on the service.
Body
| Field | Description |
|---|---|
protocol | Required. Use http for this request. |
port | Application port. HTTP gateways and Kubernetes services use port 80 automatically. |
custom_fqdn | Hostname to attach, such as app.example.com. |
curl --fail-with-body -X POST "$BASE/$SERVICE/ingress" \
-H "Authorization: Bearer $SIMPLIFYD_API_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"protocol": "http", "port": 80, "custom_fqdn": "app.example.com"}'const response = await fetch(`${BASE}/${SERVICE}/ingress`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIMPLIFYD_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({"protocol": "http", "port": 80, "custom_fqdn": "app.example.com"}),
});
const result = await response.json();
if (!response.ok) throw new Error(result.message ?? response.statusText);Response
201 Created.
{
"success": true,
"is_wildcard": false,
"cname": "z-0192e7a0-55c1-7b3e-8f2d-4a1b6c9d0e16.simplifyd.app",
"wildcard_cname": "",
"wildcard_base_domain": "",
"dns_zone": "",
"dns_zone_pending": false,
"dns_records": [],
"dns_error": ""
}For external DNS, point your hostname at cname. The response includes dns_zone and dns_records when the platform writes records to a managed zone. Check dns_error for a DNS write failure and dns_zone_pending for a zone awaiting delegation. For wildcard validation records, see Wildcard custom domains.
Remove a custom domain from an HTTP service
DELETE {BASE}/{service}/ingress/{hostname}Detaches one custom domain from the service. This is the API equivalent of the trash icon next to a domain in the console. {hostname} is the domain, such as app.example.com, or its slug from the list response. There is no changeset to apply.
Other domains on the same port keep working. If the domain was the port's last hostname, the port is removed too.
curl --fail-with-body -X DELETE "$BASE/$SERVICE/ingress/app.example.com" \
-H "Authorization: Bearer $SIMPLIFYD_API_TOKEN"const response = await fetch(`${BASE}/${SERVICE}/ingress/app.example.com`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.SIMPLIFYD_API_TOKEN}` },
});
const result = await response.json();
if (!response.ok) throw new Error(result.message ?? response.statusText);Response
201 Created.
{
"success": true
}A hostname that is not on the service returns 404, so a typo is never reported as a removal. After removing a domain, delete its CNAME record at your DNS provider. Records in a zone hosted here are removed for you.
Attach a custom domain to a static site
POST {BASE}/{service}/site/domains
Content-Type: application/jsonAdds a hostname to the site alongside any existing domains.
Body
| Field | Description |
|---|---|
domain | Required hostname to attach. |
curl --fail-with-body -X POST "$BASE/$SERVICE/site/domains" \
-H "Authorization: Bearer $SIMPLIFYD_API_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"domain": "www.example.com"}'const response = await fetch(`${BASE}/${SERVICE}/site/domains`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIMPLIFYD_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({"domain": "www.example.com"}),
});
const result = await response.json();
if (!response.ok) throw new Error(result.message ?? response.statusText);Response
200 OK.
{
"display_name": "Docs",
"domain_cname_target": "example-site.simplifyd.app",
"custom_domains": [
{
"slug": "0192e7a0-55c1-7b3e-8f2d-4a1b6c9d0e15",
"domain": "www.example.com",
"status": "pending",
"created_at": "2026-01-15T12:00:00Z"
}
]
}The response is trimmed. For external DNS, point your hostname at domain_cname_target. Check the domain entry for dns_zone or dns_error when Simplifyd Cloud manages the zone.
Errors
| Status | When |
|---|---|
400 | The request is missing a required field, the domain is invalid, or the protocol does not support custom domains. |
401 | The project token is missing or invalid. |
402 | The workspace is suspended when creating a service. |
403 | The workspace, project, or environment is outside the token scope. |
404 | The service does not exist, or the hostname being removed is not attached to it. |