Summary

When you check a domain through the name.com Core API, a single "locked" flag can't tell you what's actually going on. A domain blocked from transferring and a domain removed from DNS both show up as locked, even though one is fine and the other is offline. To know which, you need to read the list of specific lock codes the API returns, watch for an expiry timestamp on the automatic 60-day transfer lock new domains get, and subscribe to name.com's lock-change webhook to catch the lock types the standard lookup never shows. This guide walks through each lock type, what it means for your domains, and how to monitor for them reliably in production.

Domain lock state in the name.com API (Core) is surfaced through two fields on the domain object: a locked boolean and a locks array, both returned by GET /core/v1/domains/{domainName}. A domain with locked: true and a locks array containing only clientTransferProhibited is in a distinctly different operational state than one where clientHold is present, even though both read locked: true. At scale, conflating the two causes you to treat a transfer-blocked domain the same as one that's suspended from DNS resolution. name.com powers domain flows for Vercel, Netlify, and Replit, and the behavior described here is what you'll see in production.

The locked boolean and locks array

The locked boolean and locks array serve different purposes, and both must be inspected to understand a domain's full lock state. locked is a boolean fast-check: when true, a transfer lock is active. Treat it as a quick filter, not the complete picture. You should always inspect the locks array directly to determine which specific lock codes are present.

locks is an array of string codes representing the specific lock types currently applied. When no locks are active, the field is still present in the response as an empty array. Your parsing logic should account for this, because a locks key that doesn't exist and a locks key that contains [] are two different things to handle in code.

A third field, transferLockExpiresAt, appears on the domain object when an ICANN-mandated transfer lock is in force. It's an ISO 8601 UTC timestamp indicating when the lock expires. This field is absent when no policy lock with a known expiry is active, which can happen even when locked is true if the domain is under a voluntary user lock rather than an ICANN-enforced one.

JSON
{
  "domainName": "example.com",
  "locked": true,
  "locks": ["clientTransferProhibited"],
  "transferLockExpiresAt": "2025-10-15T14:23:00Z"
}
JSON
{
  "domainName": "example.com",
  "locked": false,
  "locks": []
}

Lock states exist because domain management operates through a chain of authority: registries (Verisign for .com, for example) enforce registry-level status codes, registrars like name.com enforce client-level codes, and registrants control what falls within registrar policy. Server-enforced codes (serverTransferProhibited, serverHold, and similar) operate at the registry layer and are not surfaced through the name.com Core API. This article covers only what the API exposes.

The locks array: lock codes and what they mean

Two lock codes appear in the locks array on the single-domain GET response.

Lock Code

When Applied

Effect

locked Value

clientTransferProhibited

Default on new registration and inbound transfer, also set by voluntary user lock

Blocks outbound transfer to another registrar

true

clientHold

Applied by name.com for serious policy violations or abusive activity

Domain removed from DNS, stops resolving entirely

true

clientTransferProhibited

clientTransferProhibited is the default lock applied automatically to every newly registered domain and every inbound transfer. While this code is in the locks array, an outbound transfer request to another registrar will be rejected.

The 60-day ICANN transfer lock is a specific enforcement of this code. After a new registration, a transfer-in, or a material registrant contact change, ICANN mandates that the domain cannot be transferred for 60 days. During this window, transferLockExpiresAt is present in the response and shows the exact expiry timestamp. You can't lift this lock via the API until that timestamp passes. A PATCH request to remove the lock will be rejected while the ICANN window is still active.

Handle auth code behavior during the transfer lock window explicitly. GET /core/v1/domains/{domainName}:getAuthCode may still return a valid auth code while clientTransferProhibited is active and transferLockExpiresAt is set. Auth code retrieval isn't blocked, but any transfer request using that code will fail while the lock is active. If no auth code is available, the endpoint returns HTTP 406 with:

JSON
{"message": "No authcode found"}

Locked during the 60-day window with transferLockExpiresAt present:

JSON
{
  "domainName": "example.com",
  "locked": true,
  "locks": ["clientTransferProhibited"],
  "transferLockExpiresAt": "2025-09-28T11:00:00Z"
}

clientHold

clientHold means the domain has been removed from DNS entirely. It won't resolve, and any website, email, or dependent service is offline. Name.com applies clientHold for serious policy violations such as DNS abuse or fraud. It's a DNS enforcement action, not a billing lock. Billing suspension follows a different path.

You're unlikely to encounter clientHold in normal operation, but treat it as a high-severity signal in monitoring code and handle it explicitly. When clientHold appears in the locks array, contact name.com directly. No API operation lifts it.

Domain under clientHold:

JSON
{
  "domainName": "example.com",
  "locked": true,
  "locks": ["clientHold"]
}

Lock types not in the locks array

Several lock types documented by name.com don't appear in the locks array and can't be inspected via the domain GET endpoint: RegistrarLock, AccountLock, TransferLock, PrivacyLock, VerificationClientHold, VerificationHold, and ExpirationClientHold. All of them trigger the domain.lock.status_change webhook, making webhook subscription the only programmatic way to observe these lock events in real time. If your integration needs to detect any of these then polling the domain GET endpoint won't help. Subscribe to the webhook.

Reading lock state programmatically

Two complementary approaches cover most integrations: webhook-driven for real-time notification of lock changes, and polling for cases where you need a full state snapshot. Use both together for production-grade domain monitoring.

Webhook-driven monitoring

The domain.lock.status_change webhook fires for every lock type, including all the types that don't surface in the locks array. This makes it the only reliable real-time signal for RegistrarLock, AccountLock, VerificationClientHold, and the rest. For clientTransferProhibited and clientHold, the webhook complements the domain GET: use it for immediate notification rather than waiting for a polling cycle to catch the change.

Webhook payloads include a lockType field identifying the specific lock code and a registryStatuses field reflecting current registry-level status. Subscribe to this event if your platform needs to react to lock changes as they happen, for example to notify a customer that their domain is under a verification hold before they wonder why their DNS isn't resolving.

Polling with GET /core/v1/domains/{domainName}

When you need a full lock state snapshot on demand, poll the single-domain GET endpoint. The locks array is populated only on this endpoint. The List Domains endpoint (GET /core/v1/domains) always returns locks as an empty array, so don't use it for lock state inspection or monitoring.

A minimal Python implementation checks lock state, flags clientHold as high-severity, and logs the ICANN transfer lock expiry when present:

python
import requests

def check_domain_lock(domain_name: str, username: str, api_token: str) -> dict:
    url = f"https://api.dev.name.com/core/v1/domains/{domain_name}"
    response = requests.get(url, auth=(username, api_token))
    response.raise_for_status()

    domain = response.json()
    locked = domain.get("locked", False)
    locks = domain.get("locks", [])  # Always present in single-domain GET, may be empty
    transfer_lock_expires = domain.get("transferLockExpiresAt")  # Absent if no ICANN policy lock

    result = {
        "locked": locked,
        "locks": locks,
        "transfer_lock_expires_at": transfer_lock_expires,
        "alert": None,
    }

    if not locked:
        return result  # locks is empty, no further inspection needed

    # clientHold means the domain is offline (high severity)
    if "clientHold" in locks:
        result["alert"] = (
            "HIGH: clientHold active, domain removed from DNS. "
            "Contact name.com immediately."
        )

    # Log ICANN transfer lock expiry when present
    if transfer_lock_expires and not result["alert"]:
        result["alert"] = f"Transfer lock active until {transfer_lock_expires}"

    return result

# use your function
if __name__ == "__main__":
    result = check_domain_lock(
        domain_name="example.com",
        username="your-username-test",
        api_token="your-api-token"
    )

    print(f"Locked: {result['locked']}")
    print(f"Active locks: {result['locks']}")
    if result['transfer_lock_expires_at']:
        print(f"Transfer lock expires: {result['transfer_lock_expires_at']}")
    if result['alert']:
        print(f"Alert: {result['alert']}")

Your output could look similar to this, depending on whether you have any locks enabled on your domain:

Bash
Locked: True
Active locks: ['clientTransferProhibited']

Two parsing notes worth calling out: locks is always present in single-domain GET responses, so use domain.get("locks", []) as a safe default rather than checking for key existence. An absent transferLockExpiresAt means no ICANN policy lock with a known expiry is active, but the domain may still be locked under a voluntary lock, so always check locked independently.

Getting started in sandbox

The name.com sandbox environment uses separate credentials from production and a separate base URL. Append -test to your username (e.g., reseller123-test), generate a sandbox API token independently (sandbox tokens aren't shared with production), and target https://api.dev.name.com. Allow up to 15 minutes for sandbox activation after provisioning.

The sandbox doesn't sync with production data. GET /core/v1/domains/{domainName} returns 404 unless the domain was registered within the sandbox environment. You can only query domains that exist in sandbox.

Register a test domain in sandbox via POST /core/v1/domains, call GET /core/v1/domains/{domainName} against https://api.dev.name.com, and inspect the response. A freshly registered sandbox domain should return locked: true, locks: ["clientTransferProhibited"], and transferLockExpiresAt with a timestamp reflecting the end of the 60-day ICANN transfer lock window. That confirms the ICANN transfer lock applied automatically on registration, which is exactly the state your production monitoring code needs to handle.

Provision your credentials at docs.name.com/docs/api-overview. The full domain GET field schema (including all optional fields, omission behavior, and the complete list of locks values) is at docs.name.com/api/v1/reference/domains/get-domain.

No sales call, no subscription required. Start for free.

Frequently asked questions

What does locked: true mean on a name.com domain object?

locked: true means at least one lock is currently active on the domain. It's a fast-check boolean, but it doesn't identify which lock type is applied. Always inspect the locks array alongside it to determine whether the domain is blocked from transfer (clientTransferProhibited), removed from DNS (clientHold), or both.

What’s the difference between clientTransferProhibited and clientHold?

clientTransferProhibited blocks outbound transfer to another registrar but leaves the domain fully functional for DNS resolution. clientHold removes the domain from DNS entirely, taking any website, email, or dependent service offline. Both appear in the locks array and set locked: true, but they require completely different responses.

Why is transferLockExpiresAt sometimes absent even when locked is true?

transferLockExpiresAt appears only when an ICANN-mandated policy lock is in force, such as the 60-day window after a new registration or inbound transfer. A voluntary user lock also sets locked: true and adds clientTransferProhibited to the locks array, but it carries no ICANN-enforced expiry, so the field is omitted from the response.

Can I retrieve an auth code while clientTransferProhibited is active?

Yes. GET /core/v1/domains/{domainName}:getAuthCode may return a valid auth code even while the transfer lock is active. Auth code retrieval itself isn’t blocked, but any transfer request submitted using that code will fail until the lock is lifted. If no auth code is available, the endpoint returns HTTP 406 with {"message": "No authcode found"}

Why does the List Domains endpoint return an empty locks array?

GET /core/v1/domains always returns locks as an empty array regardless of actual lock state. Lock state inspection requires the single-domain endpoint: GET /core/v1/domains/{domainName}. Using the list endpoint for lock monitoring will misreport every domain as unlocked.

How do I detect lock types that don’t appear in the locks array?

Seven lock types (including RegistrarLock, AccountLock, VerificationClientHold, and ExpirationClientHold) are not surfaced by the domain GET endpoint at all. The domain.lock.status_change webhook is the only programmatic way to observe these. Subscribe to that event if your integration needs to detect verification holds or account-level locks in real time.

What should I do when clientHold appears in the locks array?

Treat clientHold as a high-severity signal. The domain is offline and no API operation lifts this lock. Contact name.com directly to resolve it. In monitoring code, surface clientHold as an immediate alert distinct from the routine transfer lock handling you’d apply to clientTransferProhibited.