Support
DomainsRegister Domains

Register a domain

View as Markdown

Register 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:create scope
  • 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(&quote)
        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.

  1. 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)
    }
  2. 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(&reg)
        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

StatusMeaning
CONFIRMEDAccepted, processing in progress. Keep polling.
EXECUTINGActively executing. Keep polling.
COMPLETEDTerminal — succeeded. domain and expiresAt are populated.
FAILEDTerminal — 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:

  1. Check availability:

    gddy domain available example.com
  2. Review the required agreements for the TLD:

    gddy domain agreements --tld com
  3. Inspect TLD-specific schema requirements:

    gddy domain schema com
  4. Register the domain:

    gddy domain purchase example.com --agree --confirm

    Registration is paid and not reversible. Run without --agree first to review the required agreements, and use --dry-run to preview before confirming.

    Use gddy domain contacts init to save default contacts for repeated purchases — the command writes a local contacts.toml template that the purchase command can use.

Registration body fields

The following table lists the fields required for the registration body.

FieldRequiredDescription
quoteTokenYesToken from POST /v3/domains/registration-quotes.
domainYesFully-qualified domain name to register.
periodYesRegistration period in years (must match the quoted period).
consentYesICANN consent object capturing the agreement type, consenting party, and timestamp listed in the Consent object fields table.
Idempotency-Key headerYesUUID generated per request.

The following table lists the fields required for the consent object.

FieldRequiredDescriptionNote
agreementTypesYesAgreement type keys from the TLD's required agreements (for example, API_DPA).Get these from the requiredAgreements field in the quote response.
agreedAtYesActual 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.

StatusMost likely causeRecommended action
400Malformed request body or missing required field.Inspect fields[] in the error response.
403Credential lacks write access, no payment method on file, or account billing issue.Check code to distinguish.
404TLD not supported.
409Domain is already registered (by anyone).
422 QUOTE_MISMATCHThe 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_KEYSWrong agreementTypes value.Use the keys returned in requiredAgreements from the quote response.
422 MISSING_CONTACTNo registrant contact on the account profile.Add a registrant contact to the account profile.
422 MISSING_BILLING_PHONEContact phone is missing or malformed.Verify phone.countryCode and phone.nationalNumber are both set.
422Domain isn't available, missing or invalid consent, or other contact validation failure.Inspect fields[] for specifics.

Additional information

Agent & Automation Notes

PermissionsDomain Registration
Scopesdomains.domain:read, domains.domain:create
Rate limitRate-limited per credential per window. Go to /docs/api-users/rate-limits for current values.
IdempotentNo
DestructiveNo
On failureRegistration charges the account and is not reversible. Use the Idempotency-Key header to prevent duplicate purchases. If the operation status is EXECUTING, poll — do not resubmit.

Last updated on

How is this guide?

On this page