# Building reliable integrations (https://developer.godaddy.com/en/docs/api-users/building-reliable-integrations)



## 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:

```json
{
  "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:

```bash
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:

1. A request times out or returns a `5xx`.
2. Before retrying: call `GET /v1/domains/{domain}` (or the relevant read endpoint).
3. If the operation succeeded (domain is present, `renewAuto` changed, status is `PENDING_TRANSFER`), do not retry.
4. 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](https://developer.godaddy.com/docs/api-users/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: 1399
```

### Handling 429 in code

The only correct response to a `429` is to wait. The `Retry-After` header gives the exact number of seconds.

```js
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 `limit` values 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.

<Mermaid
  chart="
graph TD
A[POST request] -->|202 Accepted| B[Operation object returned]
B -->|Poll GET operation URL| C{Status?}
C -->|EXECUTING| C
C -->|COMPLETE| D[Success — read result]
C -->|FAILED| E[Handle error]
"
/>

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:

```python
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](https://developer.godaddy.com/docs/api-users/auth) for token generation steps and scope reference.
