Building reliable integrations
View as MarkdownError handling, rate limiting, idempotency, and retry patterns for the GoDaddy Domains API — the operational considerations that separate working code from production-ready code.
Overview
This page covers the operational patterns for building an integration that holds up in production — how to handle errors correctly, manage rate limits, avoid duplicate charges through idempotency, and implement retry logic that doesn't make failures worse. The patterns here apply to the GoDaddy Domains API specifically but reflect general principles for working with any REST API.
Error handling
The GoDaddy API returns a consistent JSON error envelope on every failure:
{
"code": "DOMAIN_NOT_AVAILABLE",
"message": "The domain you requested is not available.",
"fields": [
{
"code": "REQUIRED",
"message": "is required",
"path": "contactRegistrant.email",
"pathRelated": ""
}
]
}The code field is the key identifier. HTTP status codes alone are not sufficient — two 422 responses can require completely different handling depending on their code. Always read code before branching on a failure.
Status code guide
The following table describes the different status codes.
| Status | What it means in practice |
|---|---|
200, 201, 202, 204 | Success. 202 Accepted means the operation is async — poll for completion. |
400 | Your request is malformed. Check fields[] for which field failed validation. |
401 | Token is missing, expired, or revoked. Regenerate and retry. |
403 | Token is valid but lacks the required scope, or the account isn't eligible. |
404 | The resource doesn't exist, or the account can't see it. Don't retry without verifying state. |
409 | Conflict — something about the current state prevents the operation. |
422 | Request structure is valid but violates a business rule. Check code to determine the reason. |
429 | Rate limit exceeded. Wait for Retry-After seconds. |
5xx | Server error. Safe to retry with exponential backoff. |
What to retry and what not to
The key distinction is safe vs. unsafe operations:
| Retry safety | Operations | Explanation |
|---|---|---|
| Safe to retry unconditionally | GET | Read-only. No side effects (a timeout can always be retried). |
| Safe to retry with idempotency | PUT, PATCH on domain settings (lock state, auto-renew, nameservers) | Idempotent (replaying with the same body produces the same result). |
| Unsafe to retry without checking state first | POST for registration, renewal, or transfer | These charge the account. A network timeout doesn't mean the operation failed. Always GET the resource before retrying. |
Idempotency
Idempotency means that making the same request multiple times produces the same result as making it once. For read operations and most updates, this is automatic. For registrations, it requires explicit action.
The Idempotency-Key header
The registration endpoint (POST /v3/domains/registrations) requires an Idempotency-Key header. Generate a UUID per request and include it on every registration call:
curl -X POST "https://api.godaddy.com/v3/domains/registrations" \
-H "Authorization: Bearer $GODADDY_PAT" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ ... }'If you send the same Idempotency-Key on a retry, the server returns the original response without creating a second order. This is the safe way to retry a registration after a network timeout.
Checking state before retrying (transfer and renewal)
Transfer and renewal endpoints do not support Idempotency-Key. For those operations, check state before retrying any money-moving operation:
- A request times out or returns a
5xx. - Before retrying: call
GET /v1/domains/{domain}(or the relevant read endpoint). - If the operation succeeded (domain is present,
renewAutochanged, status isPENDING_TRANSFER), do not retry. - If the operation didn't happen, retry the original request.
This pattern avoids double-charges on endpoints where idempotency keys aren't available.
Rate limiting
The API enforces per-credential rate limits (see Rate limits for current values). When the limit is exceeded, the API returns:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Limit: 600
RateLimit-Remaining: 0
RateLimit-Reset: 1399Handling 429 in code
The only correct response to a 429 is to wait. The Retry-After header gives the exact number of seconds.
async function callWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const res = await fetch(url, options);
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get("Retry-After") ?? "10", 10);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
continue;
}
return res;
}
throw new Error("Max retries exceeded");
}Staying within limits
The rate limit is generous for interactive use cases but can be consumed quickly by bulk operations. For batch jobs:
- Spread requests over time — don't send a burst of requests in the first second and then wait for the window to reset.
- Use the list endpoints with maximum
limitvalues to reduce the number of calls needed. - Cache read results where the underlying data changes slowly (for example, domain lock state rarely changes so there's no need to poll it every minute).
Async operations
Some operations complete asynchronously. The API returns 202 Accepted immediately with an operation object, and you poll until the operation reaches a terminal state.
Nameserver replacement is the primary async operation in the Domains v3 API. The response includes a Location header pointing to the operation URL. Poll that URL until status is COMPLETE or FAILED.
For domain registration, the operation completes synchronously in most cases, but the domain may briefly appear in EXECUTING status — particularly for first-time purchases on new accounts. Poll GET /v1/domains/{domain} until status is ACTIVE.
Exponential backoff
For retryable errors (5xx, 429 after the wait period), use exponential backoff with jitter rather than fixed-interval retries. Fixed-interval retries from many clients simultaneously create retry storms that make server problems worse.
A simple backoff pattern:
import time, random
def retry_with_backoff(fn, max_attempts=4, base_delay=1.0):
for attempt in range(max_attempts):
try:
return fn()
except RetryableError as e:
if attempt == max_attempts - 1:
raise
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
time.sleep(delay)The delays would be approximately: 1s, 2s, 4s — with up to 1 second of random jitter added to each. This spreads retry load across time and avoids synchronized retry waves.
Secrets management
PATs are long-lived credentials that grant real access to real accounts. Treat them like passwords:
- Never hardcode tokens in source code or commit them to version control.
- Store tokens in environment variables or a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.).
- Use separate tokens for separate environments — a production token should never appear in a development codebase.
- Rotate tokens on a regular schedule and immediately on suspected compromise.
- Scope tokens to the minimum permissions required for the integration.
Go to Authenticate for token generation steps and scope reference.
Related
Last updated on
How is this guide?
Domain management concepts
The concepts behind domain registration, DNS, WHOIS, and the domain lifecycle — what the terms mean and why they matter when building with the GoDaddy Domains API.
Search domain availability
Check whether a domain is available to register, or get natural-language suggestions for alternatives. Both operations are available through the v3 API.