Buy a domain
POST /v1/domain-purchases — charge your card and register a hostname for your end-user.
POST /v1/domain-purchases
Charges your workspace's saved Stripe card, then registers the hostname at the upstream registrar with the contact you supply. Returns the purchase record on success. Auto-refunds and reports failure if the registrar refuses after the charge succeeded.
Request
curl -X POST https://api.domainee.dev/v1/domain-purchases \
-H "Authorization: Bearer $DOMAINEE_API_KEY" \
-H "content-type: application/json" \
-d '{
"hostname": "janesbakery.com",
"years": 1,
"registrant": {
"firstName": "Jane",
"lastName": "Smith",
"email": "jane@bakery.example",
"phone": "+1.5551234567",
"address1": "123 Main St",
"city": "Portland",
"stateOrProvince": "OR",
"postalCode": "97201",
"country": "US"
},
"customerReference": "user_jane_42"
}'
Body parameters
| Field | Required | Default | Notes |
|---|---|---|---|
hostname | ✅ | — | The domain to register. Lowercased + trimmed automatically. |
years | 1 | Integer 1–10. How long to register for. | |
registrant | ✅ | — | Contact info for the legal owner of record. See below. |
enableWhoisPrivacy | true | Free WHOIS privacy at the registrar. Set false only if you need public WHOIS. | |
autoRenew | false | Renew at the registrar automatically before expiry. | |
maxTotalCents | — | Safety ceiling — if live price exceeds this, we return 409 price_changed before charging. Recommended on every call to protect against premium-price surprises. | |
customerReference | — | Opaque string echoed back on reads + webhooks. Stash your internal user-id, order-id, etc. — then filter the list endpoint by it later. | |
autoConnect | — | When present, after registration we ALSO provision the hostname on Domainee's edge and set the registrar DNS to a single CNAME pointing at us. One API call ships a working live domain. See below. |
autoConnect object
"autoConnect": {
"originUrl": "https://janesbakery.acmesites.app", // required
"keepHost": false // optional, default false
}
| Field | Required | Notes |
|---|---|---|
originUrl | ✅ | Where the edge proxies requests. Must be https://. |
keepHost | Forward the customer's original Host header. Default false. |
If autoConnect is set, the response's purchase.connectedDomainId field
is populated with the matching /v1/domains row id, and the domain
resolves to your app within a minute of DNS propagation.
If any step in the chain fails (edge provisioning, DNS update), the
entire transaction rolls back — the Stripe charge is refunded, the
purchase row is marked refunded, and domain_purchase.failed fires.
Registrant object
All fields are forwarded to the registrar verbatim. Your end-user is the legal owner of record, not you. ICANN requires the address fields to be real — fake data risks suspension of the domain.
| Field | Required | Notes |
|---|---|---|
firstName | ✅ | |
lastName | ✅ | |
email | ✅ | Registrar may send verification mail here. |
phone | ✅ | E.164 with dot: +1.5551234567. |
address1 | ✅ | |
address2 | ||
city | ✅ | |
stateOrProvince | ✅ | |
postalCode | ✅ | |
country | ✅ | ISO 3166-1 alpha-2 (e.g. "US"). |
organization | Set for business-owned domains. |
Response — 201 Created
{
"purchase": {
"id": "f8a0c1b9-1234-…",
"workspaceId": "ws_…",
"hostname": "janesbakery.com",
"years": 1,
"wholesaleCents": 1418,
"feeCents": 100,
"totalCents": 1518,
"currency": "USD",
"registrar": "namecheap",
"registrarDomainId": "182739",
"status": "completed",
"registrant": {
"firstName": "Jane",
"lastName": "Smith",
"email": "jane@bakery.example",
"country": "US"
},
"whoisPrivacyEnabled": true,
"autoRenew": false,
"expiresAt": "2027-05-17T15:42:11.000Z",
"customerReference": "user_jane_42",
"createdAt": "2026-05-17T15:42:09.000Z",
"updatedAt": "2026-05-17T15:42:11.000Z"
}
}
Errors
| Code | Status | When |
|---|---|---|
unavailable | 409 | Hostname is already registered or not available for this TLD. |
price_changed | 409 | Live price exceeded maxTotalCents. No charge made. |
billing_required | 402 | The Stripe charge failed — declined card, no card on file, 3DS required, insufficient funds. No registration attempted. |
registration_failed | 502 | We charged your card but the registrar refused. We automatically refunded the Stripe charge and marked the row as refunded. The error message contains the registrar's reason. |
bad_request | 400 | Validation failure — missing/malformed body. |
Side effects
- A row is written to
domain_purchasesfor your workspace. - Your workspace's Stripe card is charged the full
totalCents. - The hostname is registered at the upstream registrar with the supplied contact as the legal owner.
- One of two webhook events fires:
domain_purchase.completed— happy pathdomain_purchase.failed— charge failed or registrar refused
Idempotency
Send an Idempotency-Key header to dedupe retries:
Idempotency-Key: 8a04...
We cache the response for 24 hours per workspaceId × method × path × key.
Retries with the same key return the original response without re-charging
or re-registering.
After the purchase
The domain is registered but not yet configured to serve your app's content. To make it serve traffic from your platform:
- Update DNS at the registrar to point the hostname at Domainee's edge (one CNAME, see the Custom Domains docs).
- Call
POST /v1/domainswith the same hostname so we mint a TLS cert and start proxying.
We're considering adding an autoConnect field that does both in one call.
Tell us if you want it.