Register a domain
View as MarkdownRegister a domain using the GoDaddy Domains API.
Overview
This guide covers registering a domain programmatically using the GoDaddy Domains API.
Registration charges the account's billing method and isn't reversible. Make sure you have a payment method on file before registering.
WHOIS accuracy
ICANN requires that registrant contact information be accurate. Providing false contact information can result in domain suspension. Use WHOIS privacy after registration if you want to protect your contact details. Don't enter fake data.
Prerequisites
- a GoDaddy account with a payment method on file
Note
Go to Create account to create a GoDaddy account and add or update payment methods.
- a PAT with
domains.domain:createscope - a registrant contact with name, email, phone, and mailing address
- a confirmed available domain
- (optional) the GoDaddy CLI installed and configured
Register a domain
v3 separates pricing from execution. You get a price quote first, locking a quoteToken, then execute the registration with that token. This ensures the price you agreed to is the price charged. Always poll at least once even if the initial response appears synchronous.
Note
v3 does not accept inline payment or registrant contact overrides. The billing method and registrant contact are resolved from the account profile. If the account is missing a payment method or required contact fields, the quote returns 422 with field-level details.
Check availability
Confirm the domain is available before quoting.
-
Run the availability check:
curl -s "https://api.godaddy.com/v3/domains/check-availability?domain=example.com" \ -H "Authorization: Bearer $GODADDY_PAT"const res = await fetch( "https://api.godaddy.com/v3/domains/check-availability?domain=example.com", { headers: { Authorization: `Bearer ${process.env.GODADDY_PAT}` } } ); const { available } = await res.json(); if (!available) throw new Error("Domain not available");import os, requests res = requests.get( "https://api.godaddy.com/v3/domains/check-availability", params={"domain": "example.com"}, headers={"Authorization": f"Bearer {os.environ['GODADDY_PAT']}"}, ) data = res.json() assert data["available"], "Domain not available"package main import ( "encoding/json" "fmt" "net/http" "os" ) func main() { req, _ := http.NewRequest("GET", "https://api.godaddy.com/v3/domains/check-availability?domain=example.com", nil) req.Header.Set("Authorization", "Bearer "+os.Getenv("GODADDY_PAT")) res, _ := http.DefaultClient.Do(req) defer res.Body.Close() var data struct{ Available bool `json:"available"` } json.NewDecoder(res.Body).Decode(&data) if !data.Available { panic("not available") } fmt.Println("available") }
Note
Go to Search availability for bulk checks and suggestions.
Get a registration quote
POST /v3/domains/registration-quotes locks the price and returns the requiredAgreements list you need for the next step. The quoteToken guarantees the price you see is the price charged at registration.
-
Get a quote:
curl -s -X POST "https://api.godaddy.com/v3/domains/registration-quotes" \ -H "Authorization: Bearer $GODADDY_PAT" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com", "period": 1}'const res = await fetch("https://api.godaddy.com/v3/domains/registration-quotes", { method: "POST", headers: { Authorization: `Bearer ${process.env.GODADDY_PAT}`, "Content-Type": "application/json", }, body: JSON.stringify({ domain: "example.com", period: 1 }), }); const { quoteToken, expiresAt, requiredAgreements } = await res.json(); console.log("Quote locked until", expiresAt);import os, requests res = requests.post( "https://api.godaddy.com/v3/domains/registration-quotes", json={"domain": "example.com", "period": 1}, headers={ "Authorization": f"Bearer {os.environ['GODADDY_PAT']}", "Content-Type": "application/json", }, ) res.raise_for_status() quote = res.json() required_agreements = quote["requiredAgreements"] print("Quote locked until", quote["expiresAt"])package main import ( "bytes" "encoding/json" "fmt" "net/http" "os" ) func main() { body := bytes.NewReader([]byte(`{"domain": "example.com", "period": 1}`)) req, _ := http.NewRequest("POST", "https://api.godaddy.com/v3/domains/registration-quotes", body) req.Header.Set("Authorization", "Bearer "+os.Getenv("GODADDY_PAT")) req.Header.Set("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() var quote struct { QuoteToken string `json:"quoteToken"` ExpiresAt string `json:"expiresAt"` RequiredAgreements []struct { AgreementType string `json:"agreementType"` Title string `json:"title"` URL string `json:"url"` } `json:"requiredAgreements"` } json.NewDecoder(res.Body).Decode("e) fmt.Println("Quote locked until", quote.ExpiresAt) }{ "quoteToken": "qt_abc123...", "expiresAt": "2026-01-15T10:45:00.000Z", "requiredAgreements": [ { "agreementType": "API_DPA", "title": "API Domain Purchase Agreement", "url": "https://www.godaddy.com/agreements/showdoc?pageid=reg_sa" } ], "items": [ { "domain": "example.com", "period": 1, "price": 1199990, "currency": "USD" } ] }
Note
The quoteToken expires after a short window. If it expires before you execute, fetch a new quote.
Review the required agreements
The quote response includes a requiredAgreements list (the legal agreements you need to review before you submit the registration). Each item has a title for display and a url linking to the full legal text.
-
Display each agreement to the user:
for (const agreement of requiredAgreements) { console.log(`Review: ${agreement.title}`); console.log(`Full text: ${agreement.url}`); }for agreement in required_agreements: print(f"Review: {agreement['title']}") print(f"Full text: {agreement['url']}")for _, agreement := range quote.RequiredAgreements { fmt.Println("Review:", agreement.Title) fmt.Println("Full text:", agreement.URL) } -
After the user confirms, capture the agreement types and consent timestamp:
const agreedAt = new Date().toISOString(); const agreedTypes = requiredAgreements.map((a) => a.agreementType);from datetime import datetime, timezone agreed_at = datetime.now(timezone.utc).isoformat() agreed_types = [a["agreementType"] for a in required_agreements]agreedAt := time.Now().UTC().Format(time.RFC3339) agreedTypes := make([]string, len(quote.RequiredAgreements)) for i, a := range quote.RequiredAgreements { agreedTypes[i] = a.AgreementType }
ICANN compliance
Use the actual timestamp of when the user clicked accept for agreedAt — not the time of the API call. Synthetic timestamps violate ICANN policy.
Agreements vary by TLD
Required agreements depend on the TLD. .ca domains require a CIRA agreement, .au domains require an AURA agreement. Always use the agreementType values from requiredAgreements — don't hardcode them.
Submit the registration
Send the quoteToken and consent to execute the registration. Always include an Idempotency-Key header — if the request times out or returns 5xx, replay with the same key and the server deduplicates.
Charges to your account
POST /v3/domains/registrations charges the account's billing method. Verify the domain name and the quoted price before executing.
-
Submit the registration request:
curl -s -X POST "https://api.godaddy.com/v3/domains/registrations" \ -H "Authorization: Bearer $GODADDY_PAT" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "quoteToken": "qt_abc123...", "domain": "example.com", "period": 1, "consent": { "agreedAt": "2026-01-15T10:30:00.000Z", "agreementTypes": ["API_DPA"] } }'import { randomUUID } from "node:crypto"; const res = await fetch("https://api.godaddy.com/v3/domains/registrations", { method: "POST", headers: { Authorization: `Bearer ${process.env.GODADDY_PAT}`, "Content-Type": "application/json", "Idempotency-Key": randomUUID(), }, body: JSON.stringify({ quoteToken: "qt_abc123...", domain: "example.com", period: 1, consent: { agreedAt, // timestamp captured in step 3 agreementTypes: agreedTypes, // types from requiredAgreements in step 3 }, }), }); if (!res.ok) throw new Error(`Registration failed: ${res.status}`); const { registrationId, domain } = await res.json(); console.log("Registered:", domain, "registrationId:", registrationId);import os, uuid, requests res = requests.post( "https://api.godaddy.com/v3/domains/registrations", json={ "quoteToken": "qt_abc123...", "domain": "example.com", "period": 1, "consent": { "agreedAt": agreed_at, # timestamp captured in step 3 "agreementTypes": agreed_types, # types from requiredAgreements in step 3 }, }, headers={ "Authorization": f"Bearer {os.environ['GODADDY_PAT']}", "Content-Type": "application/json", "Idempotency-Key": str(uuid.uuid4()), }, ) res.raise_for_status() data = res.json() print("Registered:", data["domain"], "registrationId:", data["registrationId"])package main import ( "bytes" "encoding/json" "fmt" "net/http" "os" "time" "github.com/google/uuid" ) func main() { body := map[string]any{ "quoteToken": "qt_abc123...", "domain": "example.com", "period": 1, "consent": map[string]any{ "agreedAt": agreedAt, // timestamp captured in step 3 "agreementTypes": agreedTypes, // types from requiredAgreements in step 3 }, } b, _ := json.Marshal(body) req, _ := http.NewRequest("POST", "https://api.godaddy.com/v3/domains/registrations", bytes.NewReader(b)) req.Header.Set("Authorization", "Bearer "+os.Getenv("GODADDY_PAT")) req.Header.Set("Content-Type", "application/json") req.Header.Set("Idempotency-Key", uuid.NewString()) res, _ := http.DefaultClient.Do(req) defer res.Body.Close() if res.StatusCode != 202 { panic(fmt.Sprint("registration failed: ", res.StatusCode)) } var reg struct { RegistrationID string `json:"registrationId"` Domain string `json:"domain"` } json.NewDecoder(res.Body).Decode(®) fmt.Println("Registered:", reg.Domain, "registrationId:", reg.RegistrationID) }
Consent and idempotency requirements
Use the actual consent timestamp for agreedAt; synthetic values violate ICANN policy. Use the agreementTypes keys from requiredAgreements in the quote response; the registration returns INVALID_AGREEMENT_KEYS if they don't match. The period must match the quoted period, or you get QUOTE_MISMATCH. The Idempotency-Key must be a unique UUID per attempt; retrying with the same key is safe after a timeout.
Poll the operation
The registration is asynchronous. Poll the returned registrationId URL every few seconds until the status reaches a terminal state.
-
Poll for the registration status:
curl -s "https://api.godaddy.com/v3/domains/registrations/{registrationId}" \ -H "Authorization: Bearer $GODADDY_PAT"async function pollRegistration(registrationId, intervalMs = 3000) { while (true) { const res = await fetch( `https://api.godaddy.com/v3/domains/registrations/${registrationId}`, { headers: { Authorization: `Bearer ${process.env.GODADDY_PAT}` } } ); const { status, domain } = await res.json(); if (status === "COMPLETED") return domain; if (status === "FAILED") throw new Error("Registration failed"); await new Promise((r) => setTimeout(r, intervalMs)); } }import os, time, requests def poll_registration(registration_id, interval=3): while True: res = requests.get( f"https://api.godaddy.com/v3/domains/registrations/{registration_id}", headers={"Authorization": f"Bearer {os.environ['GODADDY_PAT']}"}, ) data = res.json() if data["status"] == "COMPLETED": return data["domain"] if data["status"] == "FAILED": raise RuntimeError("Registration failed") time.sleep(interval)package main import ( "encoding/json" "fmt" "net/http" "os" "time" ) func pollRegistration(registrationID string) { for { req, _ := http.NewRequest("GET", "https://api.godaddy.com/v3/domains/registrations/"+registrationID, nil) req.Header.Set("Authorization", "Bearer "+os.Getenv("GODADDY_PAT")) res, _ := http.DefaultClient.Do(req) var data struct { Status string `json:"status"` } json.NewDecoder(res.Body).Decode(&data) res.Body.Close() switch data.Status { case "COMPLETED": fmt.Println("Registration complete") return case "FAILED": panic("Registration failed") } time.Sleep(3 * time.Second) } }
Operation statuses
| Status | Meaning |
|---|---|
CONFIRMED | Accepted, processing in progress. Keep polling. |
EXECUTING | Actively executing. Keep polling. |
COMPLETED | Terminal — succeeded. domain and expiresAt are populated. |
FAILED | Terminal — failed. |
You can also poll through GET /v3/domains/operations/{operationId}. Both endpoints resolve the same resource.
Use the CLI
After you set up the CLI:
-
Check availability:
gddy domain available example.com -
Review the required agreements for the TLD:
gddy domain agreements --tld com -
Inspect TLD-specific schema requirements:
gddy domain schema com -
Register the domain:
gddy domain purchase example.com --agree --confirmRegistration is paid and not reversible. Run without
--agreefirst to review the required agreements, and use--dry-runto preview before confirming.Use
gddy domain contacts initto save default contacts for repeated purchases — the command writes a localcontacts.tomltemplate that the purchase command can use.
Registration body fields
The following table lists the fields required for the registration body.
| Field | Required | Description |
|---|---|---|
quoteToken | Yes | Token from POST /v3/domains/registration-quotes. |
domain | Yes | Fully-qualified domain name to register. |
period | Yes | Registration period in years (must match the quoted period). |
consent | Yes | ICANN consent object capturing the agreement type, consenting party, and timestamp listed in the Consent object fields table. |
Idempotency-Key header | Yes | UUID generated per request. |
Consent object fields
The following table lists the fields required for the consent object.
| Field | Required | Description | Note |
|---|---|---|---|
agreementTypes | Yes | Agreement type keys from the TLD's required agreements (for example, API_DPA). | Get these from the requiredAgreements field in the quote response. |
agreedAt | Yes | Actual ISO 8601 timestamp of when the user consented. |
Common errors
The following table lists common errors and recommended actions. Go to Errors for the full error envelope.
| Status | Most likely cause | Recommended action |
|---|---|---|
400 | Malformed request body or missing required field. | Inspect fields[] in the error response. |
403 | Credential lacks write access, no payment method on file, or account billing issue. | Check code to distinguish. |
404 | TLD not supported. | |
409 | Domain is already registered (by anyone). | |
422 QUOTE_MISMATCH | The period or payment profile in the execute body doesn't match the quote. | Re-use the same period from the quote, or fetch a new quote. |
422 INVALID_AGREEMENT_KEYS | Wrong agreementTypes value. | Use the keys returned in requiredAgreements from the quote response. |
422 MISSING_CONTACT | No registrant contact on the account profile. | Add a registrant contact to the account profile. |
422 MISSING_BILLING_PHONE | Contact phone is missing or malformed. | Verify phone.countryCode and phone.nationalNumber are both set. |
422 | Domain isn't available, missing or invalid consent, or other contact validation failure. | Inspect fields[] for specifics. |
Additional information
- v3 Registrations —
POST /v3/domains/registrations - v3 Registration Quotes —
POST /v3/domains/registration-quotes - v3 Operations —
GET /v3/domains/operations/{operationId} - v1 registration and renewals —
POST /v1/domains/purchase
Agent & Automation Notes
domains.domain:read, domains.domain:createRelated
Last updated on
How is this guide?