Domain purchase and renewal
Covers the 10 API operations in the domains category.
| Method | Path | Description |
|---|---|---|
| GET | /v1/orgs/{orgSlug}/domains | The organization's domains (runlot domains list) |
| POST | /v1/orgs/{orgSlug}/domains | Buy a domain (runlot domains buy) |
| POST | /v1/orgs/{orgSlug}/domains/check | Look up a domain name (runlot domains check) |
| POST | /v1/orgs/{orgSlug}/domains/import | Import a domain you already own (runlot domains import) |
| GET | /v1/orgs/{orgSlug}/domains/{name} | Domain details and recent change history (runlot domains show) |
| POST | /v1/orgs/{orgSlug}/domains/{name}/renew | Renew (runlot domains renew) |
| POST | /v1/orgs/{orgSlug}/domains/{name}/auto-renew | Change the auto-renew intent |
| POST | /v1/orgs/{orgSlug}/domains/{name}/lock | Turn the transfer lock on and off (runlot domains lock|unlock) |
| POST | /v1/orgs/{orgSlug}/domains/{name}/auth-code | Check the transfer auth code (runlot domains auth-code) |
| POST | /v1/orgs/{orgSlug}/domains/{name}/sync | Re-read from the registrar now |
GET /v1/orgs/{orgSlug}/domains
Requires viewer or above.
operationId listDomains
| Status | Description | Response body |
|---|---|---|
| 200 | List | DomainList |
| 403 | — | — |
| 404 | — | — |
| 503 | no_core — a cp-public started without cp-core | Error |
POST /v1/orgs/{orgSlug}/domains
Requires admin or above — it spends money. Audit event domain.purchase (contacts are not recorded: the audit log cannot be erased and contact details are personal data).
Right after registration the NS records point at the runlot nameservers (ns1~3.runlot.app). No ownership-verification step is needed, so the domain can be connected to an app immediately after purchase. The ICANN verification email goes to the address in contact: the UI must say so before registration (§5.5).
Sending the same request again is safe. A completed domain returns 200, and a purchase that was interrupted midway resumes from the step where it stopped.
operationId purchaseDomain
Request body: application/json · object
| Status | Description | Response body |
|---|---|---|
| 200 | Domain already purchased (idempotent) | Domain |
| 201 | Purchase complete | Domain |
| 400 | — | — |
| 402 | payment_failed — the payment was declined | Error |
| 403 | — | — |
| 409 | domain_taken·domain_unavailable | Error |
| 422 | registrar_rejected — the registrar rejected a value in the request (contact details, for example). The registration never happened and retrying does not make it better. A request with corrected input has to be a new row. | Error |
| 502 | saga_incomplete·registrar_error — sending it again resumes where it stopped. The edge (Cloudflare) replaces the body of a 502 with its own HTML — a browser client never receives code. The server does not send failures that retrying cannot make better here. | Error |
| 503 | no_core·registrar_not_configured·dns_not_configured | Error |
POST /v1/orgs/{orgSlug}/domains/check
Requires member or above. Not viewer, because this single call burns a registrar API request — it looks like a read, but it goes outside.
priceCents is one year of initial registration, renewalCents is one year of renewal. They are separate because first-year discounts are common.
operationId checkDomains
Request body: application/json · object
| Status | Description | Response body |
|---|---|---|
| 200 | Availability by name | object |
| 400 | — | — |
| 403 | — | — |
| 503 | no_core·registrar_not_configured | Error |
POST /v1/orgs/{orgSlug}/domains/import
Requires admin or above. Audit domain.import. It does not call the registrar — it creates one zone, and once the customer switches the NS records at their own registrar to ns1~3.runlot.app, full DNS management moves to us (§5.6).
operationId importDomain
Request body: application/json · object
| Status | Description | Response body |
|---|---|---|
| 200 | Domain already imported (idempotent) | Domain |
| 201 | Import complete | Domain |
| 400 | — | — |
| 403 | — | — |
| 409 | domain_taken | Error |
| 503 | no_core·dns_not_configured | Error |
GET /v1/orgs/{orgSlug}/domains/{name}
Requires viewer or above. A name that belongs to another organization is a 404.
operationId getDomain
| Status | Description | Response body |
|---|---|---|
| 200 | Domains | DomainDetail |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/domains/{name}/renew
Requires admin or above. Audit domain.renew.
operationId renewDomain
Request body: application/json · object
| Status | Description | Response body |
|---|---|---|
| 200 | Renewed domain | Domain |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/domains/{name}/auto-renew
Requires admin or above. Auto-renew at the registrar stays on (§5.4) — a domain is an asset you lose if you are one day late, so we renew first and bill afterwards. The UI must say this too: otherwise a customer turns it off here and believes they have cancelled.
operationId setDomainAutoRenew
Request body: application/json · object
| Status | Description | Response body |
|---|---|---|
| 200 | Updated domain | Domain |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/domains/{name}/lock
Requires admin or above. Audit domain.lock.
operationId setDomainLock
Request body: application/json · object
| Status | Description | Response body |
|---|---|---|
| 200 | Updated domain | Domain |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/domains/{name}/auth-code
Requires admin or above. Audit domain.auth_code — the code itself is not written to the audit log: anyone holding the code can take the domain, and audit records cannot be deleted.
The way out is open at any time (§5.5). The only thing that blocks it is ICANN's 60-day registration/transfer lock, and that is shown as guidance.
operationId getDomainAuthCode
| Status | Description | Response body |
|---|---|---|
| 200 | Code | object |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/domains/{name}/sync
Requires member or above. The registrar is the source of truth for expiry, lock and NS. If the registrar answers that the domain is not in our account, the state is transferred_out — the only signal that a transfer has completed.
operationId syncDomain
| Status | Description | Response body |
|---|---|---|
| 200 | Reconciled domain | Domain |
| 403 | — | — |
| 404 | — | — |