REST API Reference

All endpoints are under /v1/. Authenticate with X-Platform-API-Key header.

Authentication

Every request must include your platform API key:

X-Platform-API-Key: th_live_your_key_here

Error Format

All errors return a structured JSON body:

{
  "error": {
    "code": "domain_taken",
    "message": "The domain mysite.com is already provisioned",
    "next_action": "Choose a different domain or check if you own this domain",
    "retryable": false
  }
}

Rate Limits

Rate limits are per-platform and vary by plan tier. Headers included on every response:

HeaderDescription
X-Rate-Limit-LimitMax requests per day
X-Rate-Limit-RemainingRequests remaining today
X-Rate-Limit-ResetSeconds until limit resets

POST /v1/domains

Provision a new custom domain.

Request

FieldTypeRequiredDescription
domainstringyesCustom domain to provision (e.g. "mysite.com")
origin_urlstringyesOrigin URL to proxy to
end_user_emailstringyesEnd user's email for billing
idempotency_keystringnoPrevent duplicate provisions on retry

Response (201)

{
  "id": "uuid",
  "domain": "mysite.com",
  "status": "pending_payment",
  "dns_instructions": {
    "type": "CNAME",
    "name": "mysite.com",
    "value": "cname.thin.host",
    "human_readable": "Set a CNAME record for mysite.com pointing to cname.thin.host"
  },
  "claim_url": "https://thin.host/claim/abc123...",
  "next_action": "Direct the user to the claim_url to complete payment and DNS setup",
  "expected_completion_seconds": 300
}

GET /v1/domains/:id

Get full domain status.

Response (200)

{
  "id": "uuid",
  "hostname": "mysite.com",
  "origin_url": "https://myapp.vercel.app",
  "status": "active",
  "dns_status": "resolved",
  "ssl_status": "active",
  "payment_status": "paid",
  "ready": true,
  "created_at": "2026-04-25T00:00:00Z",
  "updated_at": "2026-04-25T01:00:00Z"
}

PATCH /v1/domains/:id

Update the origin URL for an existing domain.

Request

{ "origin_url": "https://new-deploy.vercel.app" }

Response (200)

{
  "id": "uuid",
  "domain": "mysite.com",
  "origin_url": "https://new-deploy.vercel.app",
  "status": "active",
  "updated_at": "2026-04-25T02:00:00Z"
}

DELETE /v1/domains/:id

Release a domain. Removes proxy config and marks as released. Not reversible.

Response (200)

{
  "id": "uuid",
  "domain": "mysite.com",
  "status": "released",
  "released_at": "2026-04-25T03:00:00Z"
}

GET /v1/domains

List domains with pagination and filtering.

Query Parameters

ParamDefaultDescription
statusallFilter by status enum
page1Page number
limit50Results per page (max 100)

Response (200)

{
  "domains": [ ... ],
  "total": 42,
  "page": 1,
  "per_page": 50,
  "has_more": false
}

POST /v1/websites

Publish a new hosted website from JSON — the endpoint behind the MCP publish_website tool. Requires an API key created from the dashboard (linked to your account); unlinked platform keys get account_not_linked. The site is live at the returned url immediately.

Request

FieldTypeRequiredDescription
titlestringyesSite title
slugstringnoURL slug (site lives at /s/<slug>/); auto-generated if omitted
pagesarrayyes[{path, html}] — e.g. {"path": "/", "html": "<html>...</html>"}
assetsarrayno[{path, content_base64, content_type?}] — css/js/images/fonts, base64-encoded

Limits: 20MB per request, 200 pages, 500 assets. Asset types: css, js, png, jpg, jpeg, gif, svg, ico, webp, woff, woff2, ttf, json, xml, txt, pdf. A site with no / page gets a placeholder root.

Response (201)

{
  "id": "uuid",
  "slug": "my-site",
  "title": "My Site",
  "url": "https://thin.host/s/my-site/",
  "pages": ["/", "/about"],
  "assets": ["/css/style.css"],
  "page_count": 2,
  "asset_count": 1,
  "custom_domains": [],
  "next_action": "The site is live at ... — share that URL."
}

PATCH /v1/websites/:id_or_slug

Update a website — upserts pages/assets by path and can retitle. Paths not included are left untouched.

Request

{
  "title": "My Site v2",
  "pages": [{ "path": "/", "html": "<h1>Updated</h1>" }],
  "assets": [{ "path": "/css/style.css", "content_base64": "..." }]
}

Response (200)

{ "url": "https://thin.host/s/my-site/", "updated_pages": ["/"], "updated_assets": ["/css/style.css"] }

GET /v1/websites

List your hosted websites, newest first. Supports page and limit (max 100) query parameters.

Response (200)

{
  "websites": [ { "id": "...", "slug": "my-site", "url": "https://thin.host/s/my-site/", "page_count": 2 } ],
  "total": 1,
  "page": 1,
  "per_page": 50,
  "has_more": false
}

GET /v1/websites/:id_or_slug

Get one website, including its page paths and asset paths.

DELETE /v1/websites/:id_or_slug

Delete a website and all its pages and assets. Not reversible.

Response (200)

{ "deleted": true, "slug": "my-site" }

Domain Status Enum

StatusMeaning
pending_paymentWaiting for end user to pay via claim page
pending_dnsPaid, waiting for DNS records to propagate
pending_sslDNS verified, SSL certificate provisioning
activeDomain is live with SSL
failedProvisioning failed or expired
releasedDomain was released/deleted