Quickstart
Live domain in five minutes. Six requests, real responses, nothing to install.
Get a key
Sign in, open Settings, create a key. Use a test key first: test domains never contact a certificate authority and walk every state on a ten-second timer.
# Create a test key in Settings, or:
curl -X POST https://api.apexly.dev/v1/keys \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"name":"Staging","mode":"test"}'{
"id": "key_01M551Z48QJAYMT02C93PBX0MF",
"key": "sk_test_zbjrDw…", // shown once
"mode": "test"
}Tell us where your app lives
Set the origin once per app. Every customer domain forwards here with the original Host header. We fetch it once to confirm it answers.
curl -X PATCH https://api.apexly.dev/v1/apps/current \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-d '{"origin_url":"https://app.hireflow.com"}'{
"id": "app_01M541VYPHCHS1EMA417TKMA3K",
"origin_url": "https://app.hireflow.com",
"origin_reachable": true
}Create a domain
One request per customer hostname. The response carries the single DNS record to hand over and a connect link that walks the customer through it.
curl -X POST https://api.apexly.dev/v1/domains \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-d '{"hostname":"careers.acme.com","metadata":{"customer":"acme"}}'{
"id": "dom_01M551Z4AYJ9G7AW89XTQN0DFK",
"hostname": "careers.acme.com",
"status": "pending_dns",
"dns": {
"cname": {
"name": "careers",
"value": "d-tuih5b.cname.apexly.dev"
}
},
"connect_url": "https://connect.apexly.dev/c/kuuII7…"
}Hand over the record
Send the connect link, or show the record in your own UI. We check every 15 seconds for the first hour, then every five minutes for 72 hours.
# Poll if you like; webhooks are better.
curl https://api.apexly.dev/v1/domains/dom_01M551Z4AYJ9G7AW89XTQN0DFK \
-H "Authorization: Bearer sk_test_…"{
"id": "dom_01M551Z4AYJ9G7AW89XTQN0DFK",
"status": "verifying",
"status_reason": "sandbox",
"last_checked_at": null,
"next_check_at": "2026-10-17T13:49:25.646Z"
}Listen for domain.active
Verification, issuance and activation each fire a signed webhook. Verify the signature, read data.domain.metadata, mark your tenant live.
# Your endpoint receives:
POST /webhooks/apexly
Apexly-Signature: t=1760587971,v1=5f1c…
{
"type": "domain.active",
"data": { "domain": { "id": "dom_01M551Z4AYJ9G7AW89XTQN0DFK",
"hostname": "careers.acme.com",
"status": "active",
"metadata": { "customer": "acme" } } }
}# Reply 2xx within 10 s.
# Retries: 1m, 5m, 30m, 2h, 6h, 12h, 24hTrust the signature at your origin
Read the Host header to find the tenant. Reject any request whose X-Apexly-Signature does not verify; that is the only change your app needs.
const sig = req.headers["x-apexly-signature"];
const ts = req.headers["x-apexly-timestamp"];
const ok = verify(secret, `${ts}.${req.headers.host}`, sig);
if (!ok) return res.status(401).end();
const tenant = await tenantByHost(req.headers.host);# Headers we add on every request:
Host: careers.acme.com
X-Forwarded-Proto: https
X-Apexly-Domain-Id: dom_01M551Z4AYJ9G7AW89XTQN0DFK
X-Apexly-Timestamp: 1760587971
X-Apexly-Signature: 9a3f…