Domain purchase and renewal
Covers 10 API operations in the domains category.
| Method | Path | Description |
|---|---|---|
| GET | /v1/orgs/{orgSlug}/domains | Org'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 own (runlot domains import) |
| GET | /v1/orgs/{orgSlug}/domains/{name} | Domain information and recent change history (runlot domains show) |
| POST | /v1/orgs/{orgSlug}/domains/{name}/renew | Renews it (runlot domains renew) |
| POST | /v1/orgs/{orgSlug}/domains/{name}/auto-renew | Changes the auto-renew preference |
| POST | /v1/orgs/{orgSlug}/domains/{name}/lock | Turns the transfer lock on and off (runlot domains lock|unlock) |
| POST | /v1/orgs/{orgSlug}/domains/{name}/auth-code | Checks the transfer auth code (runlot domains auth-code) |
| POST | /v1/orgs/{orgSlug}/domains/{name}/sync | The registrar reads this again right now |
GET /v1/orgs/{orgSlug}/domains
Requires viewer or higher.
operationId listDomains
| Status code | Description | Response body |
|---|---|---|
| 200 | List | DomainList |
| 403 | — | — |
| 404 | — | — |
| 503 | no_core — cp-public started without cp-core | Error |
POST /v1/orgs/{orgSlug}/domains
admin and above — this spends money. Audited as domain.purchase (the contact is
not retained: audit logs cannot be deleted, and the contact is personal data).
Right after registration, the NS is set to Runlot's nameservers (ns1~3.runlot.app).
This means no ownership verification step is needed, and the app can be connected right
after purchase. The ICANN verification email goes to the contact address: the
screen must disclose this before registration (§5.5).
Sending the same request again is safe. For a completed domain, it returns 200, and a purchase interrupted midway resumes from the step where it stopped.
operationId purchaseDomain
Request body: application/json · object
| Status code | 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 the request values (contact info, and so on). The registration did not go through, and retrying does not help. A request with corrected input must be a new row. | Error |
| 502 | saga_incomplete, registrar_error — resending continues where it left off. The edge (Cloudflare) replaces the body of a 502 with its own HTML — browser clients do not receive code. The server does not send failures here that retrying cannot fix. | Error |
| 503 | no_core·registrar_not_configured·dns_not_configured | Error |
POST /v1/orgs/{orgSlug}/domains/check
Requires member or higher. Not viewer, because this single call hits the registrar API — it looks like a read, but it goes outbound.
priceCents covers the first year of registration, and renewalCents
covers one renewal year. They are separate because first-year discounts
are common.
operationId checkDomains
Request body: application/json · object
| Status code | 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 higher. Audited as domain.import. This does not call the
registrar — it creates a zone, and once the customer changes the NS records to
ns1~3.runlot.app at their own registrar, full DNS management becomes ours from that
point on (§5.6).
operationId importDomain
Request body: application/json · object
| Status code | 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 higher. A name from another org returns 404.
operationId getDomain
| Status code | Description | Response body |
|---|---|---|
| 200 | Domain | DomainDetail |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/domains/{name}/renew
Requires admin or higher. Audited as domain.renew.
operationId renewDomain
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 200 | Renewed domain | Domain |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/domains/{name}/auto-renew
Requires admin or higher. Auto-renewal on the registrar side stays on (§5.4) — a domain is an asset you lose if you're a day late, so we renew first and bill afterward. The UI must state this alongside it: otherwise the customer turns this off here and believes they have "canceled".
operationId setDomainAutoRenew
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 200 | The domain that changed | Domain |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/domains/{name}/lock
Requires admin or higher. Audited as domain.lock.
operationId setDomainLock
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 200 | The domain that changed | Domain |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/domains/{name}/auth-code
Requires admin or higher. Audited as domain.auth_code — the code itself is not
stored in the audit log: whoever holds the code can take the domain, and audit entries
cannot be deleted.
The way out is open immediately (§5.5). The only thing that can block it is ICANN's 60-day registration/transfer lock, and that is informational.
operationId getDomainAuthCode
| Status code | Description | Response body |
|---|---|---|
| 200 | Code | object |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/domains/{name}/sync
Requires member or higher. The registrar is the source of truth for
expiry date, lock, and NS. If the registrar responds that it is not in
our account, that is transferred_out — the only signal that a transfer
has completed.
operationId syncDomain
| Status code | Description | Response body |
|---|---|---|
| 200 | The matched domain | Domain |
| 403 | — | — |
| 404 | — | — |