Reference
API Reference
xdns.wtf exposes three REST endpoints for tenant management.
Authentication
All endpoints require a Bearer token in the Authorization header.
Authorization: Bearer <ADMIN_TOKEN>Requests with a missing or invalid token receive a 401 Unauthorized response.
Base URL
https://xdns.wtfEndpoints
POST
/api/tenantsauth requiredCreate a new tenant mapping, or upsert an existing one by slug. The operation is idempotent — re-posting the same slug updates the target origin.
Request body
{
"slug": "myapp",
"target_origin": "https://myapp.vercel.app",
"subnet_id": "optional-group-id"
}Fields
| Field | Type | Required | Description |
|---|---|---|---|
slug | string | yes | Subdomain slug. Lowercase alphanumeric + hyphens, 2–63 chars. Must not be reserved. |
target_origin | string | yes | Target origin URL. Must start with http:// or https://. Private IPs blocked. |
subnet_id | string | no | Optional group identifier. Sets X-XDNS-Subnet header on upstream requests. |
Responses
200Tenant created or updated
{
"ok": true,
"fqdn": "myapp.xdns.wtf",
"target_origin": "https://myapp.vercel.app"
}400Validation error
{ "error": "bad_slug" }
{ "error": "bad_target_origin" }
{ "error": "reserved_slug" }
{ "error": "private_origin_blocked" }401Missing or invalid token
UnauthorizedGET
/api/tenants/:slugauth requiredRetrieve the full configuration of a tenant by slug.
Responses
200Tenant found
{
"slug": "myapp",
"target_origin": "https://myapp.vercel.app",
"subnet_id": null,
"status": "active",
"created_at": 1740600000
}404Tenant not found
{ "error": "not_found" }401Missing or invalid token
UnauthorizedDELETE
/api/tenants/:slugauth requiredRemove a tenant mapping. The subdomain stops routing immediately — there is no propagation delay.
Responses
200Tenant removed
{ "ok": true }401Missing or invalid token
UnauthorizedError codes
| Code | Status | Meaning |
|---|---|---|
bad_slug | 400 | Slug fails regex or length validation |
reserved_slug | 400 | Slug is on the reserved list |
bad_target_origin | 400 | target_origin doesn't start with http(s):// |
private_origin_blocked | 400 | target_origin resolves to a private IP range |
not_found | 404 | Tenant slug doesn't exist |
Slug validation rules
- ·Regex:
^[a-z0-9-]{2,63}$ - ·Lowercase alphanumeric characters and hyphens only
- ·2–63 characters
- ·Must not appear in the reserved slugs list (see Architecture docs)