Skip to main content

Subdomain & Domain API

Kuploy provides free *.kuploy.app subdomains for your applications, custom domain registration, and domain purchasing. This API allows kuploy-cloud instances to provision and manage subdomains, custom domains, and purchased domains programmatically.

For Tenants

This documentation is for tenant operators integrating subdomain provisioning into their kuploy-cloud instances. End users typically interact with subdomains through the application UI, not directly via this API.

Overview

The subdomain API enables:

  • Checking subdomain availability
  • Reserving subdomains for instances
  • Provisioning DNS records
  • Releasing subdomains when no longer needed

The custom domain API enables:

  • Registering custom domains against your license
  • Removing custom domain registrations
  • Listing custom domains for an instance

The purchased domain API enables:

  • Registering domains purchased through a registrar
  • Removing purchased domain registrations
  • Listing purchased domains for an instance

All endpoints require authentication via your license key.

Configuration

Your kuploy-cloud instance should have these environment variables configured:

VariableDescription
LICENSE_HUB_URLBase URL of the kuploy-app instance (e.g., https://kuploy.app)
LICENSE_KEYYour license key from kuploy-app

Base URL

The subdomain API is available at:

{LICENSE_HUB_URL}/api/subdomain

For example: https://kuploy.app/api/subdomain

Authentication

All requests must include your LICENSE_KEY as the licenseKey in the request body:

{
"licenseKey": "{LICENSE_KEY}",
...
}

Endpoints

Check Availability

Check if a subdomain name is available.

POST /api/subdomain/check

Request:

{
"name": "my-app"
}

Response (available):

{
"available": true,
"name": "my-app",
"fqdn": "my-app.kuploy.app"
}

Response (unavailable):

{
"available": false,
"name": "my-app",
"fqdn": "my-app.kuploy.app",
"error": "This subdomain is already taken"
}

Reserve Subdomain

Reserve a subdomain for an instance. This does not create DNS records yet.

POST /api/subdomain/reserve

Request:

{
"name": "my-app",
"instanceId": "inst_xxxxxxxxxxxxxxxx",
"licenseKey": "lic_xxxxxxxxxxxxxxxx"
}

Response:

{
"success": true,
"subdomain": {
"id": "sub_xxxxxxxxxxxxxxxx",
"name": "my-app",
"fqdn": "my-app.kuploy.app",
"status": "pending"
}
}

Error (limit reached):

{
"success": false,
"error": "Subdomain limit reached (5). Upgrade your plan for more subdomains."
}

Provision DNS Record

Create the actual DNS record for a reserved subdomain.

POST /api/subdomain/provision

Request:

{
"subdomainId": "sub_xxxxxxxxxxxxxxxx",
"ipAddress": "203.0.113.50",
"licenseKey": "lic_xxxxxxxxxxxxxxxx"
}

Response:

{
"success": true,
"subdomain": {
"id": "sub_xxxxxxxxxxxxxxxx",
"name": "my-app",
"fqdn": "my-app.kuploy.app",
"ipAddress": "203.0.113.50",
"status": "active"
}
}
tip

DNS propagation is typically instant since Kuploy manages the authoritative DNS for kuploy.app.

Release Subdomain

Release a subdomain and delete its DNS record.

POST /api/subdomain/release

Request:

{
"subdomainId": "sub_xxxxxxxxxxxxxxxx",
"licenseKey": "lic_xxxxxxxxxxxxxxxx"
}

Response:

{
"success": true,
"message": "Subdomain my-app.kuploy.app has been released"
}

List Subdomains

List all subdomains for an instance.

POST /api/subdomain/list

Request:

{
"instanceId": "inst_xxxxxxxxxxxxxxxx",
"licenseKey": "lic_xxxxxxxxxxxxxxxx"
}

Response:

{
"success": true,
"subdomains": [
{
"id": "sub_xxxxxxxxxxxxxxxx",
"name": "my-app",
"fqdn": "my-app.kuploy.app",
"ipAddress": "203.0.113.50",
"status": "active",
"provisionedAt": "2025-01-10T12:00:00Z",
"createdAt": "2025-01-10T11:55:00Z"
}
]
}

Subdomain Status

StatusDescription
pendingReserved but DNS not yet provisioned
activeDNS record created and live
suspendedTemporarily disabled
releasedPreviously used, now available

Subdomain Naming Rules

Subdomains must follow these rules:

  • Length: 3-63 characters
  • Characters: Lowercase letters, numbers, and hyphens only
  • Format: Cannot start or end with a hyphen
  • Uniqueness: Must be unique across all Kuploy users

Reserved Names

The following subdomain names are reserved and cannot be used:

www, app, api, admin, mail, smtp, pop, imap, ftp, ssh,
ns1, ns2, dns, test, dev, staging, prod, production,
beta, alpha, status, help, support, docs, blog, cdn,
static, assets, media, images, img, dashboard, billing,
account, login, signup, register, auth, oauth, sso, kuploy

Error Codes

HTTP StatusErrorDescription
400INVALID_SUBDOMAINName doesn't meet naming rules
400INVALID_IPInvalid IPv4 address format
403LIMIT_REACHEDSubdomain limit for plan exceeded
404NOT_FOUNDSubdomain or instance not found
409ALREADY_TAKENSubdomain already reserved by another user

Integration Example

Here's a typical flow for provisioning a subdomain:

const LICENSE_HUB_URL = process.env.LICENSE_HUB_URL;
const LICENSE_KEY = process.env.LICENSE_KEY;

// 1. Check availability
const check = await fetch(`${LICENSE_HUB_URL}/api/subdomain/check`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'my-app' })
});
const { available } = await check.json();

if (!available) {
throw new Error('Subdomain not available');
}

// 2. Reserve the subdomain
const reserve = await fetch(`${LICENSE_HUB_URL}/api/subdomain/reserve`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'my-app',
instanceId: 'inst_xxx',
licenseKey: LICENSE_KEY
})
});
const { subdomain } = await reserve.json();

// 3. Provision DNS (once you know the IP)
const provision = await fetch(`${LICENSE_HUB_URL}/api/subdomain/provision`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
subdomainId: subdomain.id,
ipAddress: '203.0.113.50',
licenseKey: LICENSE_KEY
})
});

// Subdomain is now live at my-app.kuploy.app

Rate Limits

API requests are rate-limited per license:

OperationLimit
Check availability60/minute
Reserve/Provision/Release10/minute
List subdomains30/minute

Exceeding limits returns HTTP 429 with a Retry-After header.

Custom Domain API

Custom domains allow your users to attach their own domains (e.g., app.example.com) to applications. These endpoints register and manage custom domains with the license hub.

Plan Required

Custom domains require a Starter plan or higher. Requests from the Hobby plan will return a 403 error.

Base URL

{LICENSE_HUB_URL}/api/custom-domain

Register Custom Domain

Register a custom domain for an instance. Enforces plan feature flags and domain limits server-side.

POST /api/custom-domain/register

Request:

{
"host": "app.example.com",
"instanceId": "inst_xxxxxxxxxxxxxxxx",
"licenseKey": "lic_xxxxxxxxxxxxxxxx"
}

Response (success):

{
"success": true,
"customDomain": {
"id": "cd_xxxxxxxxxxxxxxxx",
"host": "app.example.com",
"status": "active"
}
}

Error (plan doesn't support custom domains):

{
"error": "Custom domains are not available on your plan. Upgrade to Starter or higher."
}

Error (limit reached):

{
"error": "Custom domain limit reached (10). Upgrade your plan for more custom domains."
}

Remove Custom Domain

Remove a custom domain registration and free up a domain slot.

POST /api/custom-domain/remove

Request:

{
"customDomainId": "cd_xxxxxxxxxxxxxxxx",
"licenseKey": "lic_xxxxxxxxxxxxxxxx"
}

Response:

{
"success": true,
"message": "Custom domain app.example.com has been removed"
}

List Custom Domains

List all custom domains registered for an instance.

POST /api/custom-domain/list

Request:

{
"instanceId": "inst_xxxxxxxxxxxxxxxx",
"licenseKey": "lic_xxxxxxxxxxxxxxxx"
}

Response:

{
"success": true,
"customDomains": [
{
"id": "cd_xxxxxxxxxxxxxxxx",
"host": "app.example.com",
"status": "active",
"createdAt": "2025-01-10T12:00:00Z"
}
]
}

Custom Domain Error Codes

HTTP StatusErrorDescription
400INVALID_HOSTInvalid domain format
403FEATURE_DISABLEDPlan doesn't include custom domains
403LIMIT_REACHEDCustom domain limit for plan exceeded
403LICENSE_SUSPENDEDLicense is suspended

Purchased Domain API

Purchased domains are domains bought through the platform's registrar integration (e.g., Namecheap). These endpoints track purchased domain registrations with the license hub for plan limit enforcement.

Plan Required

Domain purchasing requires the domainPurchase feature flag to be enabled — either via your platform license (e.g., Enterprise) or the user's subscription plan (e.g., Pro/Business). Requests without this feature return a 403 error. The kuploy-cloud instance uses a license-first, plan-fallback check before calling this API. See Domain Administration — Access Control for details.

Base URL

{LICENSE_HUB_URL}/api/purchased-domain

Register Purchased Domain

Register a domain purchased through the registrar. Enforces the domainPurchase feature flag and purchasableDomains limit server-side. Idempotent — re-registering the same host for the same instance returns the existing record.

POST /api/purchased-domain/register

Request:

{
"host": "example.com",
"instanceId": "inst_xxxxxxxxxxxxxxxx",
"licenseKey": "lic_xxxxxxxxxxxxxxxx",
"registrarId": "12345",
"expiresAt": "2027-01-15T00:00:00Z",
"autoRenew": true
}
FieldRequiredDescription
hostYesThe purchased domain name
instanceIdYesInstance that owns this domain
licenseKeyYesLicense key for authorization
registrarIdNoDomain ID from the registrar
expiresAtNoDomain expiration date (ISO 8601)
autoRenewNoWhether auto-renewal is enabled (default: true)

Response (success):

{
"success": true,
"purchasedDomain": {
"id": "pd_xxxxxxxxxxxxxxxx",
"host": "example.com",
"status": "active"
}
}

Error (feature not available):

{
"success": false,
"error": "Domain purchasing is not available on your plan. Upgrade to a plan with domain purchase enabled."
}

Error (limit reached):

{
"success": false,
"error": "Purchased domain limit reached (10). Upgrade your plan for more purchasable domains."
}

Remove Purchased Domain

Remove a purchased domain registration and free up a domain slot. This does not cancel the domain at the registrar — it only removes the license hub tracking.

POST /api/purchased-domain/remove

Request:

{
"purchasedDomainId": "pd_xxxxxxxxxxxxxxxx",
"licenseKey": "lic_xxxxxxxxxxxxxxxx"
}

Response:

{
"success": true,
"message": "Purchased domain has been removed"
}

List Purchased Domains

List all active purchased domains for an instance.

POST /api/purchased-domain/list

Request:

{
"instanceId": "inst_xxxxxxxxxxxxxxxx",
"licenseKey": "lic_xxxxxxxxxxxxxxxx"
}

Response:

{
"success": true,
"purchasedDomains": [
{
"id": "pd_xxxxxxxxxxxxxxxx",
"host": "example.com",
"status": "active",
"registrarId": "12345",
"expiresAt": "2027-01-15T00:00:00Z",
"autoRenew": true,
"createdAt": "2026-01-15T12:00:00Z"
}
]
}

Purchased Domain Error Codes

HTTP StatusErrorDescription
400MISSING_FIELDSRequired fields missing from request
403FEATURE_DISABLEDPlan doesn't include domain purchasing
403LIMIT_REACHEDPurchased domain limit for plan exceeded
403LICENSE_SUSPENDEDLicense is suspended
404NOT_FOUNDDomain or instance not found