ASCII - Data contains only ASCII characters that are not region or language specific
UTF-8 - Data contains characters that are specific to a region or language
"
- },
- "nameFirst": {
- "type": "string",
- "format": "person-name",
- "maxLength": 30
- },
- "nameMiddle": {
- "type": "string"
- },
- "nameLast": {
- "type": "string",
- "format": "person-name",
- "maxLength": 30
- },
- "organization": {
- "type": "string",
- "format": "organization-name",
- "maxLength": 100
- },
- "jobTitle": {
- "type": "string"
- },
- "email": {
- "type": "string",
- "format": "email",
- "maxLength": 80
- },
- "phone": {
- "type": "string",
- "format": "phone",
- "maxLength": 17
- },
- "fax": {
- "type": "string",
- "format": "phone",
- "maxLength": 17
- },
- "addressMailing": {
- "$ref": "#/components/schemas/Address"
- },
- "metadata": {
- "type": "object",
- "description": "The contact eligibility data fields as specified by GET /v2/customers/{customerId}/domains/contacts/schema/{tld}"
- }
+ "type": "array"
+ }
+ }
+ },
+ "description": "DNS Records to replace whatever currently exists",
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Request was successful"
+ }
+ },
+ "operationId": "recordReplaceType",
+ "summary": "Replace all DNS Records for the specified Domain with the specified Type"
+ },
+ "get": {
+ "tags": [
+ "v1"
+ ],
+ "parameters": [
+ {
+ "description": "Shopper ID which owns the domain. NOTE: This is only required if you are a Reseller managing a domain purchased outside the scope of your reseller account. For instance, if you're a Reseller, but purchased a Domain via http://www.godaddy.com",
+ "in": "header",
+ "name": "X-Shopper-Id",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Domain whose DNS Records are to be retrieved",
+ "in": "path",
+ "name": "domain",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "DNS Record Type for which DNS Records are to be retrieved",
+ "in": "path",
+ "name": "type",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "A",
+ "AAAA",
+ "CNAME",
+ "MX",
+ "NS",
+ "SOA",
+ "SRV",
+ "TXT"
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Request was successful",
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/V1DNSRecord"
+ },
+ "type": "array"
+ }
+ }
+ }
+ }
+ },
+ "operationId": "recordGetByType",
+ "summary": "Retrieve DNS Records for the specified Domain, optionally with the specified Type and/or Name"
+ }
+ },
+ "/v1/domains/{domain}/records/{type}/{name}": {
+ "get": {
+ "tags": [
+ "v1"
+ ],
+ "parameters": [
+ {
+ "description": "Shopper ID which owns the domain. NOTE: This is only required if you are a Reseller managing a domain purchased outside the scope of your reseller account. For instance, if you're a Reseller, but purchased a Domain via http://www.godaddy.com",
+ "in": "header",
+ "name": "X-Shopper-Id",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Domain whose DNS Records are to be retrieved",
+ "in": "path",
+ "name": "domain",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "DNS Record Type for which DNS Records are to be retrieved",
+ "in": "path",
+ "name": "type",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "A",
+ "AAAA",
+ "CNAME",
+ "MX",
+ "NS",
+ "SOA",
+ "SRV",
+ "TXT"
+ ]
+ }
+ },
+ {
+ "description": "DNS Record Name for which DNS Records are to be retrieved",
+ "in": "path",
+ "name": "name",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Number of results to skip for pagination",
+ "in": "query",
+ "name": "offset",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Maximum number of items to return",
+ "in": "query",
+ "name": "limit",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Request was successful",
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/V1DNSRecord"
+ },
+ "type": "array"
+ }
+ }
+ }
+ }
+ },
+ "operationId": "recordGet",
+ "summary": "Retrieve DNS Records for the specified Domain, optionally with the specified Type and/or Name"
+ },
+ "put": {
+ "tags": [
+ "v1"
+ ],
+ "parameters": [
+ {
+ "description": "Shopper ID which owns the domain. NOTE: This is only required if you are a Reseller managing a domain purchased outside the scope of your reseller account. For instance, if you're a Reseller, but purchased a Domain via http://www.godaddy.com",
+ "in": "header",
+ "name": "X-Shopper-Id",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Domain whose DNS Records are to be replaced",
+ "in": "path",
+ "name": "domain",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "DNS Record Type for which DNS Records are to be replaced",
+ "in": "path",
+ "name": "type",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "A",
+ "AAAA",
+ "CNAME",
+ "MX",
+ "NS",
+ "SOA",
+ "SRV",
+ "TXT"
+ ]
+ }
+ },
+ {
+ "description": "DNS Record Name for which DNS Records are to be replaced",
+ "in": "path",
+ "name": "name",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/V1DNSRecordCreateTypeName"
},
- "required": [
- "encoding",
- "nameFirst",
- "nameLast",
- "email",
- "phone",
- "addressMailing"
- ]
+ "type": "array"
+ }
+ }
+ },
+ "description": "DNS Records to replace whatever currently exists",
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Request was successful"
+ }
+ },
+ "operationId": "recordReplaceTypeName",
+ "summary": "Replace all DNS Records for the specified Domain with the specified Type and Name"
+ },
+ "delete": {
+ "tags": [
+ "v1"
+ ],
+ "parameters": [
+ {
+ "description": "Shopper ID which owns the domain. NOTE: This is only required if you are a Reseller managing a domain purchased outside the scope of your reseller account. For instance, if you're a Reseller, but purchased a Domain via http://www.godaddy.com",
+ "in": "header",
+ "name": "X-Shopper-Id",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "Domain whose DNS Records are to be deleted",
+ "in": "path",
+ "name": "domain",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "DNS Record Type for which DNS Records are to be deleted",
+ "in": "path",
+ "name": "type",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "A",
+ "AAAA",
+ "CNAME",
+ "MX",
+ "SRV",
+ "TXT"
+ ]
+ }
+ },
+ {
+ "description": "DNS Record Name for which DNS Records are to be deleted",
+ "in": "path",
+ "name": "name",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Request was successful"
+ }
+ },
+ "operationId": "recordDeleteTypeName",
+ "summary": "Delete all DNS Records for the specified Domain with the specified Type and Name"
+ }
+ }
+ },
+ "components": {
+ "parameters": {
+ "xRequestId": {
+ "name": "X-Request-Id",
+ "in": "header",
+ "description": "Optional client-generated request correlation identifier, propagated across services and returned in the response X-Request-Id header.\n",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ "xShopperId": {
+ "name": "X-Shopper-Id",
+ "in": "header",
+ "description": "Reseller acting on behalf of a shopper account. When present, all domain operations are scoped to the specified shopper. Absent, the authenticated entity's own account is used. Only valid for reseller OAuth tokens.\n",
+ "required": false,
+ "schema": {
+ "type": "string"
+ },
+ "example": "shopper_123"
+ },
+ "idempotencyKey": {
+ "name": "Idempotency-Key",
+ "in": "header",
+ "description": "Client-generated unique key (UUID recommended). Retrying a mutating request with the same Idempotency-Key returns the original response without creating a duplicate side effect. Required on all execute endpoints.\n",
+ "required": true,
+ "schema": {
+ "type": "string"
+ },
+ "example": "9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c"
+ },
+ "domainNamePath": {
+ "name": "domain-name",
+ "in": "path",
+ "description": "The domain name in punycode A-label form (e.g., example.com). For IDNs, use the punycode representation.\n",
+ "required": true,
+ "schema": {
+ "type": "string"
+ },
+ "example": "example.com"
+ },
+ "registrationId": {
+ "name": "registrationId",
+ "in": "path",
+ "description": "Server-assigned registration identifier.",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/uuid"
+ }
+ },
+ "operationId": {
+ "name": "operationId",
+ "in": "path",
+ "description": "The server-assigned operation identifier returned in the 202 response of any async domain mutation.\n",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/uuid"
+ },
+ "example": "9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c"
+ },
+ "zonePath": {
+ "name": "zone",
+ "in": "path",
+ "description": "The domain name in punycode A-label form (e.g., example.com). For IDNs, use the punycode representation.\n",
+ "required": true,
+ "schema": {
+ "type": "string"
+ },
+ "example": "example.com"
+ }
+ },
+ "headers": {
+ "xRequestId": {
+ "description": "Request correlation identifier echoed from the request or server-generated.",
+ "schema": {
+ "$ref": "#/components/schemas/uuid"
+ }
+ },
+ "location": {
+ "description": "URL of the created or async resource.",
+ "schema": {
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "retryAfter": {
+ "description": "Suggested number of seconds before the client should poll again. Present on 202 responses and non-terminal operation poll responses.\n",
+ "schema": {
+ "type": "integer",
+ "example": 5
+ }
+ }
+ },
+ "responses": {
+ "400": {
+ "description": "Malformed request syntax, missing required field, or invalid field type.",
+ "headers": {
+ "X-Request-Id": {
+ "$ref": "#/components/headers/xRequestId"
+ }
+ },
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authentication credentials are missing or invalid.",
+ "headers": {
+ "X-Request-Id": {
+ "$ref": "#/components/headers/xRequestId"
+ }
+ },
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Authenticated identity is not authorized to perform this operation.",
+ "headers": {
+ "X-Request-Id": {
+ "$ref": "#/components/headers/xRequestId"
+ }
+ },
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "The requested resource was not found.",
+ "headers": {
+ "X-Request-Id": {
+ "$ref": "#/components/headers/xRequestId"
+ }
+ },
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "Conflict \u2014 the request cannot be completed in the current state. Used for quote lifecycle errors (quote_expired, quote_mismatch, quote_consumed, consent_principal_mismatch) and domain state conflicts such as domain_already_exists.\n",
+ "headers": {
+ "X-Request-Id": {
+ "$ref": "#/components/headers/xRequestId"
+ }
+ },
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Semantically invalid request \u2014 valid structure but violates a business rule, such as an ineligible contact, unsupported TLD, or non-renewable domain status.\n",
+ "headers": {
+ "X-Request-Id": {
+ "$ref": "#/components/headers/xRequestId"
+ }
+ },
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ },
+ "429": {
+ "description": "Too many requests \u2014 rate limit exceeded.",
+ "headers": {
+ "X-Request-Id": {
+ "$ref": "#/components/headers/xRequestId"
+ }
+ },
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "Service temporarily unavailable.",
+ "headers": {
+ "X-Request-Id": {
+ "$ref": "#/components/headers/xRequestId"
+ },
+ "Retry-After": {
+ "$ref": "#/components/headers/retryAfter"
+ }
+ },
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ }
+ },
+ "schemas": {
+ "Agreement": {
+ "title": "Agreement",
+ "description": "A legal agreement that must be accepted prior to executing a domain operation. Agreements are returned in the quote response and must be acknowledged in the execute request via the consent.agreementTypes array.\n",
+ "type": "object",
+ "properties": {
+ "agreementType": {
+ "description": "The type of legal agreement. Identifies which agreement text the customer must accept.\n",
+ "example": "DNRA",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/AgreementType"
+ }
+ ]
+ },
+ "title": {
+ "type": "string",
+ "description": "Human-readable title of the agreement, suitable for display to the customer.\n",
+ "example": "Domain Name Registration Agreement"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "description": "URL to the full legal text of this agreement. Present when available.",
+ "example": "https://www.godaddy.com/agreements/showdoc?pageid=reg_sa"
+ }
+ }
+ },
+ "Availability": {
+ "title": "Availability",
+ "description": "The availability check result for a single requested domain. A checkable domain returns the available flag plus optional pricing. A domain that could not be checked carries an error object and no availability fields. Exactly one of available or error is present per item.\nAvailability is best-effort indicative; the authoritative availability check is performed at quote time. definitive: true means the result was confirmed directly with the registry rather than from a cached zone check.\n",
+ "type": "object",
+ "properties": {
+ "domain": {
+ "type": "string",
+ "description": "The domain name checked, normalized to punycode A-label form.\n",
+ "example": "example.com"
+ },
+ "unicodeDomain": {
+ "type": "string",
+ "description": "The Unicode (U-label) form of the domain. Present only for IDN domains.\n",
+ "example": "m\u00fcnchen.de"
+ },
+ "available": {
+ "type": "boolean",
+ "description": "Whether this domain appears to be available for registration. Best-effort; re-verified at quote time. Present only when the domain was successfully checked (no error).\n"
+ },
+ "definitive": {
+ "type": "boolean",
+ "description": "When true, the availability result was confirmed directly with the registry (ACCURACY mode). When false, the result is from a cached zone data check (SPEED mode) and may be stale.\n"
+ },
+ "inventory": {
+ "description": "The inventory source for this domain. Present when available is true and pricing fields are returned.\n",
+ "example": "REGISTRY",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/InventoryType"
+ }
+ ]
+ },
+ "prices": {
+ "type": "array",
+ "description": "Indicative pricing offered per registration term length, in ascending order of period. Present when available is true. Prices are best-effort indicative and are locked only at quote time.\n",
+ "items": {
+ "$ref": "#/components/schemas/TermPrice"
},
- "DNSRecord": {
- "properties": {
- "data": {
- "type": "string"
- },
- "name": {
- "format": "domain",
- "type": "string"
- },
- "port": {
- "description": "Service port (SRV only)",
- "maximum": 65535,
- "minimum": 1,
- "type": "integer"
- },
- "priority": {
- "description": "Record priority (MX and SRV only)",
- "format": "integer-positive",
- "type": "integer"
- },
- "protocol": {
- "description": "Service protocol (SRV only)",
- "type": "string"
- },
- "service": {
- "description": "Service type (SRV only)",
- "type": "string"
- },
- "ttl": {
- "format": "integer-positive",
- "type": "integer"
- },
- "type": {
- "enum": [
- "A",
- "AAAA",
- "CNAME",
- "MX",
- "NS",
- "SOA",
- "SRV",
- "TXT"
- ],
- "type": "string"
- },
- "weight": {
- "description": "Record weight (SRV only)",
- "format": "integer-positive",
- "type": "integer"
- }
- },
- "required": [
- "type",
- "name",
- "data"
- ]
+ "nullable": true
+ },
+ "error": {
+ "description": "Present when this domain could not be checked. One of available or error is present, never both. correlationId is required and should echo the X-Request-Id header value for this request.\nCommon name values (aligned with v2 find API error codes): MISMATCH_FORMAT \u2014 the domain name does not conform to the expected format. UNSUPPORTED_TLD \u2014 the TLD is not supported for this account or check. INVALID_DOMAIN \u2014 the availcheck service reported a syntax error for this domain. ERROR_UNKNOWN \u2014 the check failed and could not be classified further.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/error"
+ }
+ ]
+ }
+ }
+ },
+ "AvailabilityCheckCriteria": {
+ "title": "Availability Check Criteria",
+ "description": "Criteria for an availability check. Specifies 1\u201350 domain names and optional parameters that influence how the check is performed. This controller does not persist the check; there is no check identity or poll URL.\n",
+ "type": "object",
+ "required": [
+ "domains"
+ ],
+ "properties": {
+ "domains": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 50,
+ "items": {
+ "type": "string"
},
- "DNSRecordCreateType": {
- "properties": {
- "data": {
- "type": "string"
- },
- "name": {
- "format": "domain",
- "type": "string"
- },
- "port": {
- "description": "Service port (SRV only)",
- "maximum": 65535,
- "minimum": 1,
- "type": "integer"
- },
- "priority": {
- "description": "Record priority (MX and SRV only)",
- "format": "integer-positive",
- "type": "integer"
- },
- "protocol": {
- "description": "Service protocol (SRV only)",
- "type": "string"
- },
- "service": {
- "description": "Service type (SRV only)",
- "type": "string"
- },
- "ttl": {
- "format": "integer-positive",
- "type": "integer"
- },
- "weight": {
- "description": "Record weight (SRV only)",
- "format": "integer-positive",
- "type": "integer"
- }
- },
- "required": [
- "name",
- "data"
- ]
+ "description": "List of 1\u201350 domain names to check, in punycode A-label form for IDNs.\n",
+ "example": [
+ "example.com",
+ "example.net"
+ ]
+ },
+ "optimizeFor": {
+ "default": "SPEED",
+ "description": "Optional. When omitted, defaults to SPEED. Availability is always re-verified authoritatively at quote time regardless of this setting.\n",
+ "example": "SPEED",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/OptimizationTarget"
+ }
+ ]
+ },
+ "iscCode": {
+ "type": "string",
+ "description": "Reseller ISC (International Shopper Code) for pricing context. When provided, the indicative prices in the results reflect the applicable reseller rates for this ISC.\n",
+ "example": "ISC_PARTNER_001"
+ }
+ }
+ },
+ "Consent": {
+ "title": "Consent",
+ "description": "Customer consent record for a domain operation, capturing which legal agreements were accepted, when, and by whom. This object is self-reported by the caller and treated as supplementary attestation. The server verifies agreedBy.principal against the authenticated identity (auth token + X-Shopper-Id resolution) and rejects with consent_principal_mismatch on disagreement. The persisted consent record is the union of this claimed block and the verified auth context.\n",
+ "type": "object",
+ "required": [
+ "agreementTypes",
+ "agreedAt",
+ "agreedBy"
+ ],
+ "properties": {
+ "agreementTypes": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/components/schemas/AgreementType"
},
- "DNSRecordCreateTypeName": {
- "properties": {
- "data": {
- "type": "string"
- },
- "port": {
- "description": "Service port (SRV only)",
- "maximum": 65535,
- "minimum": 1,
- "type": "integer"
- },
- "priority": {
- "description": "Record priority (MX and SRV only)",
- "format": "integer-positive",
- "type": "integer"
- },
- "protocol": {
- "description": "Service protocol (SRV only)",
- "type": "string"
- },
- "service": {
- "description": "Service type (SRV only)",
- "type": "string"
- },
- "ttl": {
- "format": "integer-positive",
- "type": "integer"
- },
- "weight": {
- "description": "Record weight (SRV only)",
- "format": "integer-positive",
- "type": "integer"
- }
- },
- "required": [
- "data"
- ]
+ "description": "The agreement types the customer accepted. Must match the agreementType values returned in the corresponding quote's requiredAgreements array.\n",
+ "example": [
+ "DNRA"
+ ]
+ },
+ "agreedAt": {
+ "description": "The timestamp at which the principal expressed consent. Should reflect when the customer clicked accept or confirmed the operation, not when the API call was made.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/date-time"
+ }
+ ]
+ },
+ "agreedBy": {
+ "$ref": "#/components/schemas/ConsentActor"
+ }
+ }
+ },
+ "ConsentActor": {
+ "title": "Consent Actor",
+ "description": "Identifies who gave consent and who transmitted it. One uniform schema for all actor types. Self-reported by the caller and treated as supplementary attestation; the server verifies principal against the resolved auth identity (OAuth token + X-Shopper-Id) and rejects with consent_principal_mismatch on disagreement. The persisted consent record is the union of this block and the verified auth context.\nprincipal identifies the account holder whose consent is being recorded. actor identifies the automated or intermediary party that transmitted the consent when different from the principal. For DIRECT, actor is omitted.\n",
+ "type": "object",
+ "x-sensitivity": "confidential",
+ "required": [
+ "type",
+ "principal"
+ ],
+ "properties": {
+ "type": {
+ "description": "Who transmitted consent relative to the principal.\n",
+ "example": "DIRECT",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/ConsentActorType"
+ }
+ ]
+ },
+ "principal": {
+ "type": "string",
+ "description": "The shopper or account ID whose consent is being recorded. Must match the shopper resolved from the OAuth token and X-Shopper-Id header; mismatch returns consent_principal_mismatch.\n",
+ "example": "shopper_123"
+ },
+ "actor": {
+ "type": "string",
+ "description": "The automated agent or system that transmitted the consent. Omitted for DIRECT. For AGENT, identifies the specific agent instance. For RESELLER, may identify the reseller when distinct from the OAuth token subject.\n",
+ "example": "agent:claude/atlas-1"
+ },
+ "ip": {
+ "type": "string",
+ "description": "The IP address of the principal at the time consent was expressed, if known. Optional \u2014 an absent IP with a verified principal is preferred over a fabricated one.\n",
+ "example": "203.0.113.7"
+ }
+ }
+ },
+ "DNSRecord": {
+ "title": "DNS Record",
+ "description": "A single DNS resource record in the zone for a domain managed by GoDaddy DNS. Supports standard IANA record types plus the GoDaddy ALIAS extension. SOA records are read-only and managed by GoDaddy's authoritative DNS infrastructure. The record is uniquely identified by the combination of name, type, and data.\n",
+ "type": "object",
+ "required": [
+ "name",
+ "type",
+ "data",
+ "ttl"
+ ],
+ "properties": {
+ "recordId": {
+ "type": "string",
+ "description": "Server-assigned stable identifier for this DNS record.\n",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "description": "The DNS record name (label), relative to the zone apex. Use @ to represent the zone apex itself. Wildcards (*) are supported for A, AAAA, and CNAME records.\n",
+ "example": "@"
+ },
+ "type": {
+ "description": "The DNS resource record type. Together with name and data, uniquely identifies this record in the zone. Determines the expected data format and which optional fields (priority, service, port, etc.) apply. SOA and NS records are read-only.\n",
+ "example": "A",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/DnsRecordType"
+ }
+ ]
+ },
+ "data": {
+ "type": "string",
+ "description": "The record data, formatted per the record type. For MX: the mail exchange hostname (e.g. \"mail.example.com.\"). Supply priority as the sibling priority field. For SRV: the target hostname; supply priority, weight, port, service, and protocol as sibling fields. For TXT: the text value (quotes are handled by the DNS layer). For A/AAAA: the IP address. For CNAME/ALIAS: the target hostname with trailing dot.\n",
+ "example": "192.0.2.1"
+ },
+ "ttl": {
+ "type": "integer",
+ "minimum": 600,
+ "maximum": 86400,
+ "description": "Time-to-live in seconds. Controls how long resolvers may cache this record. Minimum 600 (10 minutes); maximum 86400 (24 hours).\n",
+ "example": 3600
+ },
+ "priority": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 65535,
+ "description": "Record priority. Required for MX and SRV records. Lower values indicate higher preference. Omit for all other record types.\n",
+ "example": 10
+ },
+ "service": {
+ "type": "string",
+ "description": "Service name for SRV and TLSA records, prefixed with an underscore (e.g. `_http`, `_smtp`). Combined with the protocol to form the record name as `_service._proto.name`.\n",
+ "example": "_http"
+ },
+ "port": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 65535,
+ "description": "TCP or UDP port number for the target service. Used in SRV records to direct clients to the correct port, and in TLSA records to identify the service endpoint being certified.\n",
+ "example": 443
+ },
+ "weight": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 65535,
+ "description": "Relative weight for load distribution among SRV records with equal priority. Higher values increase the probability of selection. Use 0 when only one target exists at a given priority.\n",
+ "example": 10
+ },
+ "protocol": {
+ "type": "string",
+ "description": "Transport protocol for SRV and TLSA records, prefixed with an underscore. Typically `_tcp` or `_udp`.\n",
+ "example": "_tcp"
+ },
+ "flag": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 255,
+ "description": "CAA record flag. Certification Authority restriction flags byte (RFC 8659; CAA only) Use 0 for non-critical, 128 for critical (issuer must understand the tag).\n",
+ "example": 0
+ },
+ "tag": {
+ "type": "string",
+ "description": "CAA record property tag. Common values: `issue` (authorize CA to issue), `issuewild` (wildcard certs), `iodef` (violation reporting URL).\n",
+ "example": "issue"
+ }
+ }
+ },
+ "NameServers": {
+ "title": "Name Servers",
+ "description": "Authoritative nameserver hostnames for a domain. ICANN requires a minimum of two nameservers; registries accept up to thirteen.\n",
+ "type": "array",
+ "minItems": 2,
+ "maxItems": 13,
+ "items": {
+ "$ref": "#/components/schemas/NameserverHostname"
+ }
+ },
+ "NameserverHostname": {
+ "title": "Nameserver Hostname",
+ "description": "A fully-qualified nameserver hostname in punycode A-label form. Labels are dot-separated; a trailing dot is not required.\n",
+ "type": "string"
+ },
+ "Domain": {
+ "title": "Domain",
+ "description": "A registered domain owned by the authenticated account. Represents the full management view of a domain: registration metadata, lifecycle status, nameservers, privacy and auto-renew preferences.\n",
+ "type": "object",
+ "properties": {
+ "domain": {
+ "type": "string",
+ "description": "The registered domain name in punycode A-label form. For IDNs, the Unicode (U-label) form is available in the idnDomain field.\n",
+ "readOnly": true,
+ "example": "example.com"
+ },
+ "idnDomain": {
+ "type": "string",
+ "description": "The Unicode (U-label) representation of the domain. Present only for internationalized domain names (IDNs).\n",
+ "readOnly": true
+ },
+ "status": {
+ "description": "The current lifecycle status of this domain.\n",
+ "readOnly": true,
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/DomainStatus"
+ }
+ ]
+ },
+ "expiresAt": {
+ "description": "The date and time when this domain registration expires. After expiry the domain enters a grace period before deletion.\n",
+ "readOnly": true,
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/date-time"
+ }
+ ]
+ },
+ "createdAt": {
+ "description": "The date and time when this domain was first registered with GoDaddy.\n",
+ "readOnly": true,
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/date-time"
+ }
+ ]
+ },
+ "renewBy": {
+ "description": "The date and time when this domain must renew on before entering expiry.\n",
+ "readOnly": true,
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/date-time"
+ }
+ ]
+ },
+ "updatedAt": {
+ "description": "The date and time when this domain was last updated\n",
+ "readOnly": true,
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/date-time"
+ }
+ ]
+ },
+ "autoRenew": {
+ "type": "boolean",
+ "description": "Whether this domain is set to auto-renew before expiry. When true, the registration is renewed automatically before it expires.\n"
+ },
+ "privacy": {
+ "type": "boolean",
+ "description": "Whether WHOIS privacy protection is active for this domain. When true, the registrant's contact details are masked in public WHOIS.\n"
+ },
+ "transferLock": {
+ "type": "boolean",
+ "description": "Whether the transfer lock (registrar lock / EPP lock) is active. When true, outbound transfers to another registrar are blocked.\n",
+ "readOnly": true
+ },
+ "nameServers": {
+ "type": "array",
+ "minItems": 2,
+ "maxItems": 13,
+ "items": {
+ "type": "string"
},
- "DomainAvailableResponse": {
- "properties": {
- "available": {
- "description": "Whether or not the domain name is available",
- "type": "boolean"
- },
- "currency": {
- "default": "USD",
- "description": "Currency in which the `price` is listed. Only returned if tld is offered",
- "format": "iso-currency-code",
- "type": "string"
- },
- "definitive": {
- "description": "Whether or not the `available` answer has been definitively verified with the registry",
- "type": "boolean"
- },
- "domain": {
- "description": "Domain name",
- "type": "string"
- },
- "period": {
- "description": "Number of years included in the price. Only returned if tld is offered",
- "format": "integer-positive",
- "type": "integer"
- },
- "price": {
- "description": "Price of the domain excluding taxes or fees. Only returned if tld is offered",
- "format": "currency-micro-unit",
- "type": "integer"
- },
- "renewalPrice": {
- "description": "Price for renewing the domain excluding taxes or fees. Only returned if tld is offered",
- "format": "currency-micro-unit",
- "type": "integer"
- }
- },
- "required": [
- "domain",
- "available",
- "definitive"
- ]
+ "description": "The current authoritative nameservers for this domain.\n",
+ "example": [
+ "ns01.domaincontrol.com",
+ "ns02.domaincontrol.com"
+ ],
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NameServers"
+ }
+ ],
+ "nullable": true
+ },
+ "links": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/link-description"
},
- "DomainContactsCreateV2": {
- "additionalProperties": false,
- "type": "object",
- "properties": {
- "admin": {
- "$ref": "#/components/schemas/ContactDomainCreate"
- },
- "adminId": {
- "description": "Unique identifier of the contact that the user wants to use for the domain admin contact. This can be specified instead of the `admin` property.\n",
- "type": "string"
- },
- "billing": {
- "$ref": "#/components/schemas/ContactDomainCreate"
- },
- "billingId": {
- "description": "Unique identifier of the contact that the user wants to use for the domain billing contact. This can be specified instead of the `billing` property.\n",
- "type": "string"
- },
- "registrant": {
- "$ref": "#/components/schemas/ContactDomainCreate"
- },
- "registrantId": {
- "description": "Unique identifier of the contact that the user wants to use for the domain registrant contact. This can be specified instead of the `registrant` property.\n",
- "type": "string"
- },
- "tech": {
- "$ref": "#/components/schemas/ContactDomainCreate"
- },
- "techId": {
- "description": "Unique identifier of the contact that the user wants to use for the domain tech contact. This can be specified instead of the `tech` property.\n",
- "type": "string"
- }
- }
+ "description": "HATEOAS link relations for this domain. rel=self \u2014 the canonical URL for this domain record. rel=contacts \u2014 the domain's WHOIS contact records. rel=nameservers \u2014 the domain's authoritative nameservers. rel=dns-records \u2014 the domain's DNS records managed by GoDaddy.\n",
+ "readOnly": true,
+ "nullable": true
+ }
+ }
+ },
+ "DomainOperation": {
+ "title": "Domain Operation",
+ "description": "The abstract operation envelope for all domain mutations, returned by the universal GET /operations/{operationId} endpoint. Concrete specializations \u2014 Registration, Renewal, and Transfer \u2014 are returned directly by their respective POST endpoints and carry the same operationId. Developers who do not need the abstract view can poll the concrete resource (GET /registrations/{id}, etc.) and ignore this type entirely.\nOperation IDs are unique across all concrete types, so either poll path works for any given operation.\nAsync state machine:\n status tracks where the operation is in its lifecycle. Non-terminal values\n (CONFIRMED, EXECUTING) are transient \u2014 poll until a terminal value is reached.\n result and error are mutually exclusive terminal payloads:\n COMPLETED \u2014 operation succeeded; result contains the final outcome data.\n FAILED \u2014 operation terminated; error contains failure detail.\n Neither result nor error is present while status is non-terminal.\n",
+ "type": "object",
+ "properties": {
+ "operationId": {
+ "description": "Stable, server-assigned identifier for this operation. Unique across all operation types. Use to poll GET /operations/{operationId}. Matches the operationId on the corresponding concrete resource (e.g. Registration).\n",
+ "readOnly": true,
+ "example": "9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/uuid"
+ }
+ ]
+ },
+ "type": {
+ "description": "The type of operation being tracked. Determines which fields appear in result on COMPLETED and the concrete resource collection (REGISTER \u2192 /registrations, RENEW \u2192 /renewals, TRANSFER_IN \u2192 /transfers).\n",
+ "readOnly": true,
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/DomainOperationType"
+ }
+ ]
+ },
+ "domain": {
+ "type": "string",
+ "description": "The domain name this operation applies to.",
+ "readOnly": true,
+ "example": "example.com"
+ },
+ "status": {
+ "description": "Current position in the operation lifecycle. Poll until COMPLETED or FAILED. Non-terminal while CONFIRMED or EXECUTING; terminal values are mutually exclusive with each other and do not revert.\n",
+ "readOnly": true,
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/DomainOperationStatus"
+ }
+ ]
+ },
+ "result": {
+ "description": "Present when status is COMPLETED. Absent while non-terminal and when status is FAILED.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/DomainOperationResult"
+ }
+ ],
+ "readOnly": true
+ },
+ "error": {
+ "description": "Present when status is FAILED. Absent while non-terminal and when status is COMPLETED.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/error"
+ }
+ ],
+ "readOnly": true
+ },
+ "links": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/link-description"
},
- "DomainPurchase": {
- "properties": {
- "consent": {
- "$ref": "#/components/schemas/Consent"
- },
- "contactAdmin": {
- "$ref": "#/components/schemas/Contact"
- },
- "contactBilling": {
- "$ref": "#/components/schemas/Contact"
- },
- "contactRegistrant": {
- "$ref": "#/components/schemas/Contact"
- },
- "contactTech": {
- "$ref": "#/components/schemas/Contact"
- },
- "domain": {
- "description": "For internationalized domain names with non-ascii characters, the domain name is converted to punycode before format and pattern validation rules are checked",
- "format": "domain",
- "type": "string"
- },
- "nameServers": {
- "items": {
- "format": "host-name",
- "type": "string"
- },
- "type": "array"
- },
- "period": {
- "default": 1,
- "format": "integer-positive",
- "maximum": 10,
- "minimum": 1,
- "type": "integer"
- },
- "privacy": {
- "default": false,
- "type": "boolean"
- },
- "renewAuto": {
- "default": true,
- "type": "boolean"
- }
- },
- "required": [
- "domain",
- "consent"
- ]
+ "description": "HATEOAS link relations for this operation. rel=self \u2014 the canonical URL for this abstract operation view. rel=registration, rel=renewal, or rel=transfer \u2014 the same resource viewed through its concrete typed collection. rel=domain \u2014 the domain-name resource affected by this operation.\n",
+ "readOnly": true,
+ "nullable": true
+ },
+ "createdAt": {
+ "description": "Timestamp when this operation was created.",
+ "readOnly": true,
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/date-time"
+ }
+ ]
+ },
+ "updatedAt": {
+ "description": "Timestamp of the most recent status update.",
+ "readOnly": true,
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/date-time"
+ }
+ ]
+ }
+ }
+ },
+ "DomainOperationResult": {
+ "title": "Domain Operation Result",
+ "description": "The terminal success payload for a completed domain operation. Returned on the parent DomainOperation when status is COMPLETED. Absent for non-terminal statuses (CONFIRMED, EXECUTING) and for FAILED operations.\nOnce status reaches COMPLETED it is terminal: result is populated, remains available on subsequent polls, and status does not revert. Interpret the fields present in result using the parent operation's type:\nREGISTER \u2014 expiresAt, orderId.\n",
+ "type": "object",
+ "readOnly": true,
+ "properties": {
+ "expiresAt": {
+ "description": "New domain expiry date. Present for REGISTER and RENEW operations.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/date-time"
+ }
+ ]
+ },
+ "orderId": {
+ "type": "string",
+ "description": "The commerce order ID associated with the charge. Present for commercial operations (REGISTER, RENEW, TRANSFER_IN).\n",
+ "example": "ord_abc123"
+ },
+ "updatedAt": {
+ "description": "Timestamp of the completed non-commercial mutation.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/date-time"
+ }
+ ]
+ }
+ }
+ },
+ "Registration": {
+ "title": "Registration",
+ "description": "A domain registration entity created when a POST /registrations request is accepted. Registrations are a top-level resource with their own stable registrationId; the domain relationship is captured in the representation.\nOn POST /registrations, supply the writable fields (domain, period, quoteToken, consent, and optionally profileId/profile). The server returns the full Registration with readOnly fields populated. Poll links[rel=self] until status reaches COMPLETED or FAILED. The same resource is also reachable via GET /operations/{operationId} for clients operating at the abstract level; operationId is included in the representation for that purpose.\n",
+ "type": "object",
+ "required": [
+ "domain",
+ "consent"
+ ],
+ "properties": {
+ "registrationId": {
+ "description": "Server-assigned stable identifier for this registration record.\n",
+ "readOnly": true,
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/uuid"
+ }
+ ]
+ },
+ "domain": {
+ "type": "string",
+ "description": "The domain name to register, in punycode A-label form for IDNs. Must match the domain in the quoteToken.\n",
+ "example": "example.com"
+ },
+ "period": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 10,
+ "default": 1,
+ "description": "Registration period in years. Must match the period in the quote.",
+ "example": 1
+ },
+ "profileId": {
+ "description": "ID of a saved registration profile to use for contacts and preference defaults. Omit to fall back to the account-default profile for the domain's TLD, then to account identity.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/uuid"
+ }
+ ]
+ },
+ "profile": {
+ "description": "One-time inline contacts and purchase preference defaults for this registration. Not persisted. Must match the profile supplied on the quote when a profile was included at quote time.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/InlineRegistrationProfile"
+ }
+ ]
+ },
+ "quoteToken": {
+ "description": "The single-use opaque token from the preceding quoteDomainRegistration call. Required. Consumed on first successful execution; idempotent retries with the same Idempotency-Key replay the original operation without re-consuming.\n",
+ "writeOnly": true,
+ "example": "7f3a2b1c-9d8e-4012-a5b6-c1d2e3f4a5b6",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/uuid"
+ }
+ ]
+ },
+ "consent": {
+ "description": "The customer's consent record for the legal agreements returned in the quote. Must reference the same agreementTypes as the quote.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Consent"
+ }
+ ]
+ },
+ "status": {
+ "description": "Current execution status of this registration.",
+ "readOnly": true,
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/DomainOperationStatus"
+ }
+ ]
+ },
+ "operationId": {
+ "description": "Identifier of the DomainOperation tracking this registration. Same value returned by GET /operations/{operationId} for abstract operation tracking.\n",
+ "readOnly": true,
+ "example": "9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/uuid"
+ }
+ ]
+ },
+ "expiresAt": {
+ "description": "The domain's expiry date once the registration completes. Present only when status is COMPLETED.\n",
+ "readOnly": true,
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/date-time"
+ }
+ ]
+ },
+ "createdAt": {
+ "description": "Timestamp when this registration was initiated.",
+ "readOnly": true,
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/date-time"
+ }
+ ]
+ },
+ "updatedAt": {
+ "description": "Timestamp of the most recent status update.",
+ "readOnly": true,
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/date-time"
+ }
+ ]
+ },
+ "links": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/link-description"
},
- "DomainPurchaseResponse": {
- "properties": {
- "currency": {
- "default": "USD",
- "description": "Currency in which the `total` is listed",
- "format": "iso-currency-code",
- "type": "string"
- },
- "itemCount": {
- "description": "Number items included in the order",
- "format": "integer-positive",
- "type": "integer"
- },
- "orderId": {
- "description": "Unique identifier of the order processed to purchase the domain",
- "format": "int64",
- "type": "integer"
- },
- "total": {
- "description": "Total cost of the domain and any selected add-ons",
- "format": "currency-micro-unit",
- "type": "integer"
- }
- }
+ "description": "HATEOAS link relations for this registration. rel=self \u2014 the canonical URL for this registration record. rel=domain \u2014 the registered domain-name resource once the registration is complete.\n",
+ "readOnly": true
+ }
+ }
+ },
+ "InlineRegistrationProfile": {
+ "title": "Inline Registration Profile",
+ "description": "A one-time, non-persisted set of contacts and purchase preference defaults supplied inline on a quote or execute request. Use to provide registration data for this transaction without creating or updating a saved registration profile.\nShared by the registration quote and execute request bodies. Every field is optional. Omitted fields account identity or other default values. Provided fields override only what is supplied \u2014 contact roles replace as a whole block; preference fields replace individually.\nThis is not a saved registration profile and is not JSON Patch. Data here applies only to the current quote or registration request.\n",
+ "type": "object",
+ "properties": {
+ "contacts": {
+ "description": "Contact records for this request. Each role provided replaces that role from the resolved saved profile or account identity. Omitted roles continue to resolve from the saved profile or cascade from registrant.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Contacts"
+ }
+ ]
+ },
+ "autoRenew": {
+ "type": "boolean",
+ "description": "Auto-renew preference for this registration. Omit to inherit from the resolved saved profile or account defaults.\n"
+ },
+ "privacy": {
+ "type": "boolean",
+ "description": "WHOIS privacy preference for this registration. Omit to inherit from the resolved saved profile or account defaults.\n"
+ },
+ "nameServers": {
+ "description": "Authoritative nameservers for this registration. Omit to inherit from the resolved saved profile or platform defaults.\n",
+ "example": [
+ "ns1.example.com",
+ "ns2.example.com"
+ ],
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NameServers"
+ }
+ ]
+ }
+ }
+ },
+ "RegistrationQuote": {
+ "title": "Registration Quote",
+ "description": "A price quote for registering a single domain. Contains a locked price, resolved contact and preference settings, required legal agreements, and a short-lived single-use quoteToken that must be presented on the subsequent registration execute call. Execution without a valid quoteToken is structurally impossible.\nWhen available is false, no quoteToken is returned \u2014 this is not an error; it means the domain cannot be registered as requested.\n",
+ "type": "object",
+ "properties": {
+ "quoteToken": {
+ "description": "Opaque, single-use token with a 10-minute TTL. References the locked price, a hash of this request body, and a hash of the resolved profile values. Absent when available is false. Treat as a capability; do not parse.\n",
+ "example": "7f3a2b1c-9d8e-4012-a5b6-c1d2e3f4a5b6",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/uuid"
+ }
+ ]
+ },
+ "expiresAt": {
+ "description": "The expiry timestamp of the quoteToken. After this time, presenting the token on execute returns quote_expired and a new quote must be obtained.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/date-time"
+ }
+ ]
+ },
+ "domain": {
+ "type": "string",
+ "description": "The domain name being quoted, in punycode A-label form for IDNs.\n",
+ "example": "example.com"
+ },
+ "available": {
+ "type": "boolean",
+ "description": "Whether the domain is available for registration. When false, no quoteToken is returned. The availability check at quote time is authoritative; a name sniped between suggest/availability and quote fails cleanly here.\n"
+ },
+ "price": {
+ "description": "The locked registration price for the quoted period. Held for the duration of the quoteToken's TTL. Represents the total amount that will be charged on execute.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/simple-money"
+ }
+ ]
+ },
+ "renewalPrice": {
+ "description": "Indicative renewal cost at current rates. Not a price guarantee; renewal pricing is locked at time of renewal quote.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/simple-money"
+ }
+ ]
+ },
+ "period": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 10,
+ "description": "Registration period in years for which the price is quoted.",
+ "example": 1
+ },
+ "resolved": {
+ "description": "The effective contact and preference settings that will be applied on execute. Review before execute to verify registrant, contacts, and preferences match intent.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/ResolvedSettings"
+ }
+ ]
+ },
+ "requiredAgreements": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Agreement"
},
- "DomainPurchaseV2": {
- "additionalProperties": false,
- "properties": {
- "domain": {
- "type": "string",
- "format": "domain",
- "pattern": "^[^.]{1,63}.[^.]{2,}$",
- "description": "For internationalized domain names with non-ascii characters, the domain name is converted to punycode before format and pattern validation rules are checked"
- },
- "consent": {
- "$ref": "#/components/schemas/ConsentV2"
- },
- "period": {
- "type": "integer",
- "format": "integer-positive",
- "default": 1,
- "minimum": 1,
- "maximum": 10,
- "pattern": "^[0-9]+$"
- },
- "nameServers": {
- "type": "array",
- "items": {
- "type": "string",
- "format": "host-name"
- },
- "maxItems": 2
- },
- "renewAuto": {
- "type": "boolean",
- "default": true
- },
- "privacy": {
- "type": "boolean",
- "default": false
- },
- "contacts": {
- "$ref": "#/components/schemas/DomainContactsCreateV2"
- },
- "metadata": {
- "type": "object",
- "description": "The domain eligibility data fields as specified by GET /v2/customers/{customerId}/domains/register/schema/{tld}"
- }
- },
- "required": [
- "domain",
- "consent"
- ]
+ "description": "Legal agreements that must be accepted before executing this quote. The agreementType values from this list must be included in the execute request's consent object.\n",
+ "nullable": true
+ },
+ "irreversible": {
+ "type": "boolean",
+ "description": "Whether executing this quote is irreversible once accepted. Use to calibrate the explicitness of any confirmation step presented before execute.\n",
+ "example": false
+ }
+ }
+ },
+ "ResolvedSettings": {
+ "title": "Resolved Settings",
+ "description": "A preview of the effective settings that will be applied if the associated quote is executed. Returned in the quote response to eliminate invisible side effects \u2014 the caller sees exactly whose contact info and which preferences will be used before making a commitment. contactSource names where the registrant contact came from so an agent can be explicit at the confirmation step.\n",
+ "type": "object",
+ "properties": {
+ "profileId": {
+ "description": "The saved registration profile that was applied, if any. Absent when the registrant was derived from account identity.\n",
+ "readOnly": true,
+ "example": "14514a29-5fce-4624-8d8a-d8abd56015e2",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/uuid"
+ }
+ ]
+ },
+ "contactSource": {
+ "description": "Indicates where the resolved registrant contact came from. When ACCOUNT, the agent should surface this clearly to the customer before confirmation.\n",
+ "example": "PROFILE",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/ContactSource"
+ }
+ ]
+ },
+ "registrantSummary": {
+ "type": "string",
+ "description": "A human-readable one-line summary of the resolved registrant, suitable for display in a confirmation prompt. When contactSource is ACCOUNT, the summary is suffixed with \"(account identity)\" to make the derivation explicit.\n",
+ "x-sensitivity": "confidential",
+ "example": "Jane Smith / jane@example.com"
+ },
+ "autoRenew": {
+ "type": "boolean",
+ "description": "The effective auto-renew setting that will be applied upon registration.\n"
+ },
+ "privacy": {
+ "type": "boolean",
+ "description": "The effective WHOIS privacy setting that will be applied upon registration.\n"
+ },
+ "nameServers": {
+ "description": "The effective nameservers that will be provisioned for the domain.\n",
+ "example": [
+ "ns01.domaincontrol.com",
+ "ns02.domaincontrol.com"
+ ],
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NameServers"
+ }
+ ]
+ }
+ }
+ },
+ "Suggestion": {
+ "title": "Suggestion",
+ "description": "A single available domain suggestion returned by the suggest endpoint. Availability is implied by presence in the results (available-only contract) and is best-effort \u2014 the availability check at quote time is the authoritative re-check. Indicative pricing may be stale; the locked price is established at quote time only.\n",
+ "type": "object",
+ "properties": {
+ "domain": {
+ "type": "string",
+ "description": "The suggested domain name in punycode A-label form.\n",
+ "example": "sunrisebakery.com"
+ },
+ "listPrice": {
+ "description": "Indicative undiscounted list price for a one-year registration. Promotional pricing and the final locked price resolve at quote time.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/simple-money"
+ }
+ ]
+ },
+ "renewalPrice": {
+ "description": "Indicative renewal price at current rates. Not a guarantee.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/simple-money"
+ }
+ ]
+ },
+ "inventory": {
+ "description": "The inventory source for this domain. Present when pricing fields are present.\n",
+ "example": "REGISTRY",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/InventoryType"
+ }
+ ]
+ }
+ }
+ },
+ "TermPrice": {
+ "title": "Term Price",
+ "description": "Pricing for one registration term, including sale and list prices for the full term and optional renewal and first-term breakdowns.\n",
+ "type": "object",
+ "properties": {
+ "term": {
+ "description": "Unit in which period is expressed. Currently only YEAR is supported.\n",
+ "example": "YEAR",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Term"
+ }
+ ]
+ },
+ "period": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 10,
+ "description": "The registration period length, in units of term.\n",
+ "example": 2
+ },
+ "price": {
+ "description": "The price for the full term covered by this price block \u2014 the current registration price for `period` units of term. Discounts or promotions may be reflected in this price.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/simple-money"
+ }
+ ]
+ },
+ "renewalPrice": {
+ "description": "Sale price that will apply when the domain renews, for the same term and period. Not a guarantee.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/simple-money"
+ }
+ ]
+ }
+ }
+ },
+ "uuid": {
+ "description": "A universally unique identifier (UUID) in [RFC-4122 format](https://tools.ietf.org/html/rfc4122).",
+ "type": "string"
+ },
+ "SuggestionSource": {
+ "title": "Suggestion Source",
+ "description": "A suggestion source strategy that generates domain name variations. EXTENSION \u2014 vary the TLD. KEYWORD_SPIN \u2014 rotate keywords. CC_TLD \u2014 vary using country-code TLDs. PREMIUM \u2014 include premium-priced variations.\n",
+ "type": "string",
+ "enum": [
+ "CC_TLD",
+ "EXTENSION",
+ "KEYWORD_SPIN",
+ "PREMIUM"
+ ]
+ },
+ "currency-code": {
+ "type": "string",
+ "title": "Currency Code",
+ "description": "A three-character ISO-4217 currency code."
+ },
+ "simple-money": {
+ "type": "object",
+ "title": "Simple Money",
+ "description": "The currency and amount for a financial transaction, such as a balance or payment due. Use for value representations with default transactable-value precision.",
+ "properties": {
+ "currencyCode": {
+ "$ref": "#/components/schemas/currency-code"
+ },
+ "value": {
+ "type": "integer",
+ "format": "int64",
+ "description": "The value, which might represent intergrals for currencies like `JPY` that are not typically fractional; or, with an implied decimal fraction for currencies like `TND` that are subdivided into thousandths. For the implied number of decimal places for a currency code, see [ISO-4217 Currency Codes](https://en.wikipedia.org/wiki/ISO_4217)."
+ }
+ }
+ },
+ "InventoryType": {
+ "title": "Inventory Type",
+ "description": "The inventory source for a domain name. REGISTRY \u2014 standard registry price inventory. REGISTRY_PREMIUM \u2014 registry premium tier pricing. PREMIUM \u2014 third-party premium domain marketplace.\n",
+ "type": "string",
+ "enum": [
+ "REGISTRY",
+ "REGISTRY_PREMIUM",
+ "PREMIUM"
+ ]
+ },
+ "error-details": {
+ "title": "Error Details",
+ "type": "object",
+ "description": "The error details. Required for client-side `4XX` errors.",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "The field that caused the error. If the field is in the body, set this value to the JSON pointer to that field. Required for client-side errors. When the offending value was resolved on the caller's behalf and has no request location, this is a source-scoped reference instead of a JSON pointer: \"shopper:\" (from the account identity) or \"profile:\" (from a saved registration profile)."
+ },
+ "value": {
+ "type": "string",
+ "description": "The value of the field that caused the error."
+ },
+ "location": {
+ "type": "string",
+ "description": "The location of the field that caused the error. Value is `body`, `path`, or `query`.",
+ "default": "body"
+ },
+ "issue": {
+ "type": "string",
+ "description": "The unique fine-grained application-level error code."
+ },
+ "description": {
+ "type": "string",
+ "description": "The human-readable description for an issue. The description MAY change over the lifetime of an API, so clients MUST NOT depend on this value."
+ }
+ }
+ },
+ "link-description": {
+ "title": "Link Description",
+ "type": "object",
+ "description": "A request-related [HATEOAS link](https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-hyperschema-02).",
+ "properties": {
+ "href": {
+ "description": "The complete target URL, or link, to use in combination with the method to make the related call, as defined by [RFC 6570 - URI Template](https://tools.ietf.org/html/rfc6570), with the addition of the `$`, `(`, and `)` characters for pre-processing. The `href` is the key HATEOAS component that links a completed call with a subsequent call.",
+ "type": "string",
+ "format": "uri"
+ },
+ "rel": {
+ "description": "The [link relation type](https://tools.ietf.org/html/rfc5988#section-4), which is an identifier for a link that unambiguously describes the semantics of the link. For values, see [Link Relationship Types](https://www.iana.org/assignments/link-relations/link-relations.xhtml).",
+ "type": "string"
+ },
+ "title": {
+ "description": "The link title.",
+ "type": "string"
+ },
+ "targetMediaType": {
+ "description": "The [RFC 2046-defined media type](https://www.ietf.org/rfc/rfc2046.txt) that describes the link target.",
+ "type": "string"
+ },
+ "targetSchema": {
+ "description": "The schema that describes the link target."
+ },
+ "method": {
+ "description": "The method to use to request the link target. For example, for HTTP, this might be `GET` or `DELETE`.",
+ "type": "string"
+ },
+ "submissionMediaType": {
+ "description": "The media type with which to submit data with the request.",
+ "type": "string",
+ "default": "application/json"
+ },
+ "submissionSchema": {
+ "description": "The schema that describes the request data."
+ }
+ }
+ },
+ "error": {
+ "type": "object",
+ "title": "Error",
+ "description": "The error information.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The human-readable, unique name of the error."
+ },
+ "correlationId": {
+ "type": "string",
+ "description": "Internal identifier used for correlation purposes."
+ },
+ "message": {
+ "type": "string",
+ "description": "The message that describes the error."
+ },
+ "informationLink": {
+ "type": "string",
+ "description": "The URI for detailed information related to this error for the developer."
+ },
+ "details": {
+ "type": "array",
+ "description": "An array of additional details about the error. Required for client-side `4XX` errors.",
+ "additionalItems": false,
+ "items": {
+ "$ref": "#/components/schemas/error-details"
},
- "DomainSuggestion": {
- "properties": {
- "domain": {
- "description": "Suggested domain name",
- "type": "string"
- }
- },
- "required": [
- "domain"
- ]
+ "nullable": true
+ },
+ "links": {
+ "type": "array",
+ "description": "An array of error-related HATEOAS links.",
+ "readOnly": true,
+ "items": {
+ "$ref": "#/components/schemas/link-description",
+ "readOnly": true
},
- "DomainSummary": {
- "properties": {
- "authCode": {
- "description": "Authorization code for transferring the Domain",
- "type": "string"
- },
- "contactAdmin": {
- "$ref": "#/components/schemas/Contact"
- },
- "contactBilling": {
- "$ref": "#/components/schemas/Contact"
- },
- "contactRegistrant": {
- "$ref": "#/components/schemas/Contact"
- },
- "contactTech": {
- "$ref": "#/components/schemas/Contact"
- },
- "createdAt": {
- "description": "Date and time when this domain was created",
- "type": "string"
- },
- "deletedAt": {
- "description": "Date and time when this domain was deleted",
- "type": "string"
- },
- "transferAwayEligibleAt": {
- "description": "Date and time when this domain is eligible to transfer",
- "type": "string"
- },
- "domain": {
- "description": "Name of the domain",
- "type": "string"
- },
- "domainId": {
- "description": "Unique identifier for this Domain",
- "format": "double",
- "type": "number"
- },
- "expirationProtected": {
- "description": "Whether or not the domain is protected from expiration",
- "type": "boolean"
- },
- "expires": {
- "description": "Date and time when this domain will expire",
- "type": "string"
- },
- "exposeWhois": {
- "description": "Whether or not the domain contact details should be shown in the WHOIS",
- "type": "boolean"
- },
- "holdRegistrar": {
- "description": "Whether or not the domain is on-hold by the registrar",
- "type": "boolean"
- },
- "locked": {
- "description": "Whether or not the domain is locked to prevent transfers",
- "type": "boolean"
- },
- "nameServers": {
- "description": "Fully-qualified domain names for DNS servers",
- "items": {
- "format": "host-name",
- "type": "string"
- },
- "type": "array",
- "nullable": true
- },
- "privacy": {
- "description": "Whether or not the domain has privacy protection",
- "type": "boolean"
- },
- "registrarCreatedAt": {
- "type": "string",
- "format": "iso-datetime",
- "description": "Date and time when this domain was created by the registrar"
- },
- "renewAuto": {
- "description": "Whether or not the domain is configured to automatically renew",
- "type": "boolean"
- },
- "renewDeadline": {
- "description": "Date the domain must renew on",
- "type": "string"
- },
- "renewable": {
- "description": "Whether or not the domain is eligble for renewal based on status",
- "type": "boolean"
- },
- "status": {
- "description": "Processing status of the domain
\n
ACTIVE - All is well
\n
AWAITING* - System is waiting for the end-user to complete an action
\n
CANCELLED* - Domain has been cancelled, and may or may not be reclaimable
\n
CONFISCATED - Domain has been confiscated, usually for abuse, chargeback, or fraud
\n
DISABLED* - Domain has been disabled
\n
EXCLUDED* - Domain has been excluded from Firehose registration
\n
EXPIRED* - Domain has expired
\n
FAILED* - Domain has failed a required action, and the system is no longer retrying
\n
HELD* - Domain has been placed on hold, and likely requires intervention from Support
\n
LOCKED* - Domain has been locked, and likely requires intervention from Support
\n
PARKED* - Domain has been parked, and likely requires intervention from Support
\n
PENDING* - Domain is working its way through an automated workflow
\n
RESERVED* - Domain is reserved, and likely requires intervention from Support
\n
REVERTED - Domain has been reverted, and likely requires intervention from Support
\n
SUSPENDED* - Domain has been suspended, and likely requires intervention from Support
\n
TRANSFERRED* - Domain has been transferred out
\n
UNKNOWN - Domain is in an unknown state
\n
UNLOCKED* - Domain has been unlocked, and likely requires intervention from Support
\n
UNPARKED* - Domain has been unparked, and likely requires intervention from Support
\n
UPDATED* - Domain ownership has been transferred to another account
\n
",
- "type": "string"
- },
- "transferProtected": {
- "description": "Whether or not the domain is protected from transfer",
- "type": "boolean"
- }
- }
+ "nullable": true
+ }
+ }
+ },
+ "OptimizationTarget": {
+ "title": "Optimization Target",
+ "description": "How an availability check should prioritize speed vs. authoritative accuracy. SPEED \u2014 use cached zone data for a fast response (may be slightly stale). ACCURACY \u2014 perform a live registry check for authoritative availability (higher latency).\n",
+ "type": "string",
+ "enum": [
+ "SPEED",
+ "ACCURACY"
+ ]
+ },
+ "Term": {
+ "title": "Term",
+ "description": "The unit of measure for a registration period. YEAR \u2014 registration period expressed in whole years.\n",
+ "type": "string",
+ "enum": [
+ "YEAR"
+ ],
+ "default": "YEAR"
+ },
+ "email-address": {
+ "description": "A valid, internationalized email address. Note: Up to 64 characters are allowed before and 255 characters are allowed after the @ sign. However, the generally accepted maximum length for an email address is 254 characters. The pattern verifies that an unquoted @ sign exists.",
+ "type": "string"
+ },
+ "phone": {
+ "type": "object",
+ "title": "Phone",
+ "description": "The phone number, in its canonical international [E.164 numbering plan format](https://www.itu.int/rec/T-REC-E.164/en).",
+ "properties": {
+ "countryCode": {
+ "type": "string",
+ "description": "The country calling code (CC), in its canonical international [E.164 numbering plan format](https://www.itu.int/rec/T-REC-E.164/en). The combined length of the CC and the national number must not be greater than 15 digits. The national number consists of a national destination code (NDC) and subscriber number (SN)."
+ },
+ "nationalNumber": {
+ "type": "string",
+ "description": "The national number, in its canonical international [E.164 numbering plan format](https://www.itu.int/rec/T-REC-E.164/en). The combined length of the country calling code (CC) and the national number must not be greater than 15 digits. The national number consists of a national destination code (NDC) and subscriber number (SN)."
+ },
+ "extensionNumber": {
+ "type": "string",
+ "description": "The extension number."
+ }
+ }
+ },
+ "country-code": {
+ "description": "A two-character ISO 3166-1 code that identifies the country or region.",
+ "type": "string"
+ },
+ "simple-address": {
+ "type": "object",
+ "title": "Simple Postal Address (Coarse-Grained)",
+ "description": "Simple postal address with coarse-grained fields. Do not use for international postal addresses. Use for backward compatibility only. Address does not contain a phone number.",
+ "properties": {
+ "line1": {
+ "type": "string",
+ "description": "The first line of the address. For example, number or street."
+ },
+ "line2": {
+ "type": "string",
+ "description": "The second line of the address. For example, suite or apartment number."
+ },
+ "city": {
+ "type": "string",
+ "description": "The city name."
+ },
+ "state": {
+ "type": "string",
+ "description": "The [code](https://about.usps.com/who/profile/history/state-abbreviations.htm) for a US state or the equivalent for other countries."
+ },
+ "countryCode": {
+ "$ref": "#/components/schemas/country-code",
+ "description": "The [two-character ISO 3166-1 code](https://en.wikipedia.org/wiki/ISO_3166-1) that identifies the country or region. Note: The country code for Great Britain is `GB` and not `UK` as used in the top-level domain names for that country. Use country code `C2` for China for comparable uncontrolled price (CUP) method, bank-card, and cross-border transactions."
+ },
+ "postalCode": {
+ "type": "string",
+ "description": "The postal code, which is the zip code or equivalent. Typically required for countries that have a postal code or an equivalent. See [Postal Code](https://en.wikipedia.org/wiki/Postal_code)."
+ }
+ },
+ "required": [
+ "line1",
+ "city",
+ "countryCode"
+ ]
+ },
+ "Contact": {
+ "title": "Contact",
+ "description": "An ICANN-required contact record for a domain registration. Covers registrant, administrative, technical, and billing roles. Identity fields are validated at registration-profile save time.\n",
+ "x-sensitivity": "confidential",
+ "type": "object",
+ "required": [
+ "firstName",
+ "lastName",
+ "email",
+ "phone",
+ "address"
+ ],
+ "properties": {
+ "firstName": {
+ "type": "string",
+ "description": "The contact's first (given) name.",
+ "x-sensitivity": "confidential",
+ "example": "Jane"
+ },
+ "lastName": {
+ "type": "string",
+ "description": "The contact's last (family) name.",
+ "x-sensitivity": "confidential",
+ "example": "Smith"
+ },
+ "organization": {
+ "type": "string",
+ "description": "Organization or company name. Required for contacts acting on behalf of a legal entity. Leave blank for individual registrants.\n",
+ "x-sensitivity": "confidential",
+ "example": "Example LLC"
+ },
+ "email": {
+ "example": "foo@bar.com",
+ "description": "The contact's email address. Used for registry WHOIS and renewal notifications.\n",
+ "x-sensitivity": "confidential",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/email-address"
+ }
+ ]
+ },
+ "phone": {
+ "description": "The contact's phone number in ITU E.164 format with GoDaddy extension notation: +{country-code}.{local-number}, e.g. +1.4805551234. Required by ICANN for all contact roles.\n",
+ "x-sensitivity": "confidential",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/phone"
+ }
+ ]
+ },
+ "address": {
+ "description": "The contact's mailing address for WHOIS and ICANN records.\n",
+ "x-sensitivity": "confidential",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/simple-address"
+ }
+ ]
+ }
+ }
+ },
+ "Contacts": {
+ "title": "Contacts",
+ "description": "The set of ICANN-required contact roles for a domain registration. Registrant is required; admin, tech, and billing cascade from the registrant when omitted. Merge rule across resolution layers (saved profile, inline registration profile): identity fields replace as a whole block per role.\n",
+ "x-sensitivity": "confidential",
+ "type": "object",
+ "required": [
+ "registrant"
+ ],
+ "properties": {
+ "registrant": {
+ "description": "The legal owner of the domain. Required. The registrant's identity is the authoritative WHOIS record and is bound by ICANN registration agreements.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Contact"
+ }
+ ]
+ },
+ "admin": {
+ "description": "The administrative contact, responsible for managing the domain on behalf of the registrant. Cascades from registrant when omitted.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Contact"
+ }
+ ]
+ },
+ "tech": {
+ "description": "The technical contact, responsible for DNS and nameserver configuration. Cascades from registrant when omitted.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Contact"
+ }
+ ]
+ },
+ "billing": {
+ "description": "The billing contact, receives invoices and renewal notices. Cascades from registrant when omitted.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Contact"
+ }
+ ]
+ }
+ }
+ },
+ "date-time": {
+ "description": "A date and time, in [Internet date and time format](https://tools.ietf.org/html/rfc3339#section-5.6). Note: The regular expression provides static schematic guidance but does not reject all invalid dates.",
+ "type": "string"
+ },
+ "ContactSource": {
+ "title": "Contact Source",
+ "description": "Where the resolved registrant contact came from. INLINE \u2014 contact was supplied via an inline registration profile on the request. PROFILE \u2014 contact was resolved from a named or default saved profile. ACCOUNT \u2014 contact was derived from the authenticated principal's account identity (no profile supplied or on file).\n",
+ "type": "string",
+ "enum": [
+ "INLINE",
+ "PROFILE",
+ "ACCOUNT"
+ ]
+ },
+ "AgreementType": {
+ "title": "Agreement Type",
+ "description": "The type of legal agreement that must be accepted prior to executing a domain operation. Additional agreement types may be returned for specific TLDs or product combinations. DNRA \u2014 Domain Name Registration Agreement. DNTA \u2014 Domain Name Transfer Agreement. DNPA \u2014 Domain Name Privacy Agreement. HTTPS_NOTICE \u2014 HTTPS notice acknowledgment for eligible TLDs. AURA \u2014 AU Domain Agreement for .au TLD registrations and transfers. CIRA \u2014 Canadian Internet Registration Authority Agreement for .ca TLD registrations and transfers.\n",
+ "type": "string"
+ },
+ "ConsentActorType": {
+ "title": "Consent Actor Type",
+ "description": "Who transmitted consent on behalf of the principal. DIRECT \u2014 the principal acted directly; actor is omitted. AGENT \u2014 an AI or automation acted on behalf of the principal. RESELLER \u2014 a reseller acted on behalf of a shopper.\n",
+ "type": "string"
+ },
+ "DomainStatus": {
+ "title": "Domain Status",
+ "description": "The lifecycle status of a registered domain. Reflects the domain's current operational state within the registry and GoDaddy's management layer. ACTIVE \u2014 domain is registered and is active. CANCELLED \u2014 domain has been cancelled by the user or system, and is not reclaimable. DELETED_REDEEMABLE \u2014 domain is in ICANN redemption grace period; recovery fees apply. EXPIRED \u2014 registration period has ended; domain is pending deletion or redemption. FAILED - domain registration or transfer error. HELD_REGISTRAR - domain is held at the registrar and cannot be transferred or modified - this is usually the result of a dispute. LOCKED_REGISTRAR \u2014 domain is locked at the registrar - this is usually the result of spam, abuse, etc. OWNERSHIP_CHANGED - domain has been moved to another account. PARKED - domain has been parked. PENDING_REGISTRATION - domain is pending setup at the registry. PENDING_TRANSFER \u2014 an outbound transfer to another registrar is in progress. REPOSSESSED - domain has been confiscated - this is usually the result of a chargeback, fraud, abuse, etc. SUSPENDED \u2014 domain has been administratively suspended by the registry or registrar. TRANSFERRED - domain has been transferred to another registrar.\n",
+ "type": "string",
+ "enum": [
+ "ACTIVE",
+ "CANCELLED",
+ "DELETED_REDEEMABLE",
+ "EXPIRED",
+ "FAILED",
+ "HELD_REGISTRAR",
+ "LOCKED_REGISTRAR",
+ "OWNERSHIP_CHANGED",
+ "PARKED",
+ "PENDING_REGISTRATION",
+ "PENDING_TRANSFER",
+ "REPOSSESSED",
+ "SUSPENDED",
+ "TRANSFERRED"
+ ]
+ },
+ "DomainOperationStatus": {
+ "title": "Domain Operation Status",
+ "description": "The execution state of an asynchronous domain operation. CONFIRMED \u2014 operation has been accepted and is queued for execution. EXECUTING \u2014 operation is actively being processed by the registry or downstream systems. COMPLETED \u2014 operation finished successfully; result data is available. FAILED \u2014 operation terminated with an unrecoverable error; error detail is attached.\n",
+ "type": "string"
+ },
+ "DomainOperationType": {
+ "title": "Domain Operation Type",
+ "description": "The type of asynchronous domain operation. Used to distinguish which workflow is being polled on the /operations/{operationId} endpoint. REGISTER \u2014 new domain registration.\n",
+ "type": "string"
+ },
+ "DnsRecordType": {
+ "title": "DNS Record Type",
+ "description": "The type of a DNS resource record. Values correspond to IANA-assigned DNS record type mnemonics. A \u2014 IPv4 address record. AAAA \u2014 IPv6 address record. CAA \u2014 certification authority authorization record. CNAME \u2014 canonical name alias record. MX \u2014 mail exchange record; data is the mail exchange hostname, priority is the sibling priority field. NS \u2014 nameserver delegation record; read-only. SOA \u2014 start of authority record; managed by the registry; read-only. SRV \u2014 service locator record. TXT \u2014 free-form text; used for SPF, DKIM, DMARC, and domain verification.\n",
+ "type": "string"
+ },
+ "V1Address": {
+ "properties": {
+ "address1": {
+ "format": "street-address",
+ "type": "string"
+ },
+ "address2": {
+ "format": "street-address2",
+ "type": "string"
+ },
+ "city": {
+ "format": "city-name",
+ "type": "string"
+ },
+ "country": {
+ "default": "US",
+ "description": "Two-letter ISO country code to be used as a hint for target region
AWAITING* - System is waiting for the end-user to complete an action
\n
CANCELLED* - Domain has been cancelled, and may or may not be reclaimable
\n
CONFISCATED - Domain has been confiscated, usually for abuse, chargeback, or fraud
\n
DISABLED* - Domain has been disabled
\n
EXCLUDED* - Domain has been excluded from Firehose registration
\n
EXPIRED* - Domain has expired
\n
FAILED* - Domain has failed a required action, and the system is no longer retrying
\n
HELD* - Domain has been placed on hold, and likely requires intervention from Support
\n
LOCKED* - Domain has been locked, and likely requires intervention from Support
\n
PARKED* - Domain has been parked, and likely requires intervention from Support
\n
PENDING* - Domain is working its way through an automated workflow
\n
RESERVED* - Domain is reserved, and likely requires intervention from Support
\n
REVERTED - Domain has been reverted, and likely requires intervention from Support
\n
SUSPENDED* - Domain has been suspended, and likely requires intervention from Support
\n
TRANSFERRED* - Domain has been transferred out
\n
UNKNOWN - Domain is in an unknown state
\n
UNLOCKED* - Domain has been unlocked, and likely requires intervention from Support
\n
UNPARKED* - Domain has been unparked, and likely requires intervention from Support
\n
UPDATED* - Domain ownership has been transferred to another account
\n
",
+ "type": "string"
+ },
+ "transferProtected": {
+ "description": "Whether or not the domain is protected from transfer",
+ "type": "boolean"
+ }
+ }
+ },
+ "V1LegalAgreement": {
+ "properties": {
+ "agreementKey": {
+ "description": "Unique identifier for the legal agreement",
+ "type": "string"
+ },
+ "content": {
+ "description": "Contents of the legal agreement, suitable for embedding",
+ "type": "string"
+ },
+ "title": {
+ "description": "Title of the legal agreement",
+ "type": "string"
+ },
+ "url": {
+ "description": "URL to a page containing the legal agreement",
+ "format": "url",
+ "type": "string"
+ }
+ }
+ }
+ },
+ "securitySchemes": {
+ "oauth2": {
+ "type": "oauth2",
+ "description": "GoDaddy OAuth 2.0 access token. The scope(s) listed on each operation are enforced per-operation. A token requires at least one of the scopes listed for that operation.\n",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "https://api.godaddy.com/v2/oauth2/authorize",
+ "tokenUrl": "https://api.godaddy.com/v2/oauth2/token",
+ "scopes": {
+ "domains.domain:read": "Read domain records, availability, suggestions, quotes, and operations.\n",
+ "domains.domain:create": "Register domains. Requires a prior quoteToken.\n",
+ "domains.nameserver:update": "Replace authoritative nameservers for a domain.\n",
+ "domains.dns:update": "Create, update, and delete DNS zone records.\n"
}
+ }
}
+ }
}
+ }
}
\ No newline at end of file
diff --git a/rust/domains-client/openapi/swagger_domains.v3.yaml b/rust/domains-client/openapi/swagger_domains.v3.yaml
new file mode 100644
index 0000000..f4b5614
--- /dev/null
+++ b/rust/domains-client/openapi/swagger_domains.v3.yaml
@@ -0,0 +1,2572 @@
+openapi: 3.0.3
+info:
+ title: Domain Lifecycle Management API
+ version: 3.0.0
+ x-visibility: public
+ description: 'The GoDaddy Domain Lifecycle Management API provides comprehensive capabilities
+
+ for discovering, registering, managing, renewing, transferring, and reselling
+
+ domain names. This is major version 3, designed for agent-first interactions
+
+ while remaining fully usable by direct API clients and resellers.
+
+
+ ## Namespace
+
+
+ All paths are under `/v3/domains/`. The namespace is `domains` (the business capability);
+
+ the core entity collection is `/domain-names`.
+
+
+ ## Key Conventions
+
+
+ **Quote/execute for commercial operations.** Every commercial mutation (register,
+
+ renew, transfer) requires a `quoteToken` minted by the corresponding quote
+
+ collection endpoint. Execution without a prior quote is structurally impossible.
+
+ The token locks the price, resolved settings, and required legal agreements for a
+
+ 10-minute TTL. Quote calls are free, read-only, and safe to call speculatively.
+
+
+ **Async commercial operations.** `POST /registrations`, `POST /renewals`, and
+
+ `POST /transfers` each return `202 Accepted` with the concrete entity body
+
+ (`Registration`, `Renewal`, or `Transfer`) and a `Location` header. Poll
+
+ `links[rel=self]` on the returned entity until status is `COMPLETED` or `FAILED`.
+
+ Each concrete resource is also reachable via `GET /operations/{operationId}`;
+
+ the `operationId` is included in the entity for clients that prefer the abstract
+
+ view. Non-commercial mutations return a `DomainOperation` body.
+
+
+ **Entity-oriented resource model.** Domains are the core entity of this API
+
+ (exposed as `/domain-names` in the path to distinguish the resource collection
+
+ from the `domains` namespace prefix). Register, renew, and transfer are
+
+ commercial actions executed by `POST` to their corresponding top-level
+
+ resource collections (`/registrations`, `/renewals`, `/transfers`); each
+
+ accepts a prior quote and returns an async entity to poll until completion.
+
+ For commercial execute calls, the target domain is
+
+ expressed in the request body, not the path. Sub-resources (`contacts`,
+
+ `nameservers`, `privacy`, `auto-renew`, `transfer-lock`, `records`) only exist
+
+ in the context of a specific domain-name instance. The `/check-availability`
+
+ controller accepts GET (single domain) or POST (1–50 domains) and carries no
+
+ persistent identity.
+
+
+ **Flat, two-level maximum.** No resource path goes deeper than
+
+ `/{collection}/{id}/{sub-resource}` or `/{collection}/{id}/{sub-collection}/{id}`.
+
+
+ **Reseller on-behalf-of.** Resellers pass `X-Shopper-Id`; all operations are
+
+ then scoped to that shopper. Absent the header, the authenticated entity''s own
+
+ account is used.
+
+
+ ## Launch Scope (v3.0)
+
+ Standard TLDs only. TLDs with eligibility requirements (.us, .ca, .eu) return
+
+ `UNSUPPORTED_TLD` until Phase 2.
+
+ '
+ contact:
+ name: GoDaddy Domains Platform
+ x-slack-channel: '#domains_beplat_eng'
+ x-visibility: private
+servers:
+- url: https://api.{environment-subdomain}.com/v3/domains
+ description: Domain Lifecycle Management API v3
+ variables:
+ environment-subdomain:
+ default: godaddy
+ description: Environment subdomain
+ enum:
+ - godaddy
+ - ote-godaddy
+ - test-godaddy
+ - dev-godaddy
+security:
+- oauth2: []
+tags:
+- name: Discovery
+ description: 'Indicative, non-committing operations for finding and checking domains. Use suggestDomains for natural-language
+ queries. For a single known domain, use getDomainAvailability (GET /check-availability); for 1–50 domains in one call,
+ use checkAvailability (POST /check-availability). Both availability operations share the same check semantics and Availability
+ result model; locked pricing is established only at quote time. Neither carries a persistent check identity.
+
+ '
+- name: Registration Quotes
+ description: 'Quote a domain registration. Returns a locked price, resolved settings, required agreements, and a single-use
+ quoteToken. Free and read-only.
+
+ '
+- name: Registrations
+ description: 'Top-level registration entity collection. Execute a domain registration by POSTing with a quoteToken, domain,
+ period, and consent. Returns a Registration entity with links to the concrete poll URL (GET /registrations/{registrationId})
+ and the abstract operation (GET /operations/{operationId}).
+
+ '
+- name: Domains
+ description: 'The core domain entity collection. Supports listing and reading owned domain records, and cancelling registrations.
+
+ '
+- name: Domain Management
+ description: 'Non-commercial async mutations on owned domain instances: contacts, nameservers, privacy, auto-renew, and
+ transfer-lock. All sub-resources of /domain-names/{domain-name}. All mutations return a DomainOperation for polling.
+
+ '
+- name: Records
+ description: 'CRUD operations on DNS records within the GoDaddy-managed zone. Sub-collection of /zones/{zone}. Changes are
+ applied synchronously.
+
+ '
+- name: Operations
+ description: 'Abstract operation polling. Poll GET /operations/{operationId} for any domain mutation until it reaches COMPLETED
+ or FAILED. Operation IDs are unique across Registration, Renewal, and Transfer — clients that prefer typed polling can
+ use the concrete resource endpoints instead.
+
+ '
+- name: Domains
+ description: 'Core domain entity collection. Retrieve registered domains owned by the authenticated account with management
+ details including status, nameservers, privacy, and auto-renew settings.
+
+ '
+paths:
+ x-visibility: public
+ /suggestions:
+ get:
+ operationId: suggestDomains
+ tags:
+ - Discovery
+ summary: Suggest available domains for a query
+ description: 'Returns available domain name suggestions for a natural-language query
+
+ or keyword set. All results are available (available-only contract).
+
+ Prices are indicative; the authoritative price and availability check
+
+ is at quote time.
+
+ '
+ parameters:
+ - $ref: '#/components/parameters/xRequestId'
+ - $ref: '#/components/parameters/xShopperId'
+ - name: query
+ in: query
+ required: false
+ description: 'Natural-language query or keywords describing the desired domain, e.g. "sunrise bakery". Used to generate
+ creative and keyword-spin suggestions.
+
+ '
+ schema:
+ type: string
+ maxLength: 100
+ example: sunrise bakery
+ - name: tlds
+ in: query
+ required: false
+ description: Top-level domains to be included in suggestions.
+ style: form
+ explode: false
+ schema:
+ type: array
+ items:
+ type: string
+ example:
+ - com
+ - net
+ - shop
+ - name: lengthMax
+ in: query
+ required: false
+ description: Maximum length of second-level domain.
+ schema:
+ type: integer
+ minimum: 1
+ - name: lengthMin
+ in: query
+ required: false
+ description: Minimum length of second-level domain.
+ schema:
+ type: integer
+ minimum: 1
+ - name: pageSize
+ in: query
+ required: false
+ description: 'Maximum number of suggestions in the response. Defaults to 10 when omitted.
+
+ '
+ schema:
+ type: integer
+ minimum: 1
+ maximum: 50
+ default: 10
+ - name: sources
+ in: query
+ required: false
+ description: 'Suggestion source strategies to activate.
+
+ '
+ style: form
+ explode: false
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/SuggestionSource'
+ example:
+ - EXTENSION
+ - KEYWORD_SPIN
+ security:
+ - oauth2:
+ - domains.domain:read
+ responses:
+ '200':
+ description: Suggested available domains sorted by relevance.
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - items
+ properties:
+ items:
+ type: array
+ description: 'Available domain suggestions, sorted by relevance. All items are available by contract.
+
+ '
+ items:
+ $ref: '#/components/schemas/Suggestion'
+ '400':
+ $ref: '#/components/responses/400'
+ '401':
+ $ref: '#/components/responses/401'
+ '403':
+ $ref: '#/components/responses/403'
+ '429':
+ $ref: '#/components/responses/429'
+ /check-availability:
+ get:
+ operationId: getDomainAvailability
+ tags:
+ - Discovery
+ summary: Check availability of a single domain
+ description: 'Returns an indicative availability result for one domain, including
+
+ per-term pricing when available. Availability is best-effort; the
+
+ authoritative check is performed at quote time. This operation does
+
+ not persist the check — there is no check identity or poll URL.
+
+
+ Equivalent to POST /check-availability with `domains: [domain]` and the
+
+ same optional criteria (`optimizeFor`, `iscCode`). The response is that
+
+ single `Availability` item unwrapped for convenience — POST returns the
+
+ same result inside `{ items: [...] }`. Use POST when checking multiple
+
+ domains in one request.
+
+
+ A domain that cannot be checked is still returned as a `200` with an
+
+ `error` object on the body (the same per-item contract as POST); request-
+
+ level failures use the `4xx` responses.
+
+ '
+ parameters:
+ - $ref: '#/components/parameters/xRequestId'
+ - $ref: '#/components/parameters/xShopperId'
+ - name: domain
+ in: query
+ required: true
+ description: The domain name to check, in punycode A-label form for IDNs.
+ schema:
+ type: string
+ example: example.com
+ - name: optimizeFor
+ in: query
+ required: false
+ description: 'Optional. When omitted, defaults to SPEED. Availability is always re-verified authoritatively at quote
+ time regardless of this setting.
+
+ '
+ schema:
+ allOf:
+ - $ref: '#/components/schemas/OptimizationTarget'
+ default: SPEED
+ example: SPEED
+ - name: iscCode
+ in: query
+ required: false
+ description: 'Reseller ISC (International Shopper Code) for pricing context. When provided, the indicative prices
+ reflect the applicable reseller rates for this ISC.
+
+ '
+ schema:
+ type: string
+ example: ISC_PARTNER_001
+ security:
+ - oauth2:
+ - domains.domain:read
+ responses:
+ '200':
+ description: Availability result for the requested domain.
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Availability'
+ examples:
+ available:
+ summary: Available domain with per-term pricing
+ value:
+ domain: coffee24x7x365.com
+ available: true
+ definitive: false
+ inventory: REGISTRY
+ prices:
+ - term: YEAR
+ period: 1
+ price:
+ currencyCode: USD
+ value: 1199
+ renewalPrice:
+ currencyCode: USD
+ value: 2299
+ - term: YEAR
+ period: 2
+ price:
+ currencyCode: USD
+ value: 3098
+ renewalPrice:
+ currencyCode: USD
+ value: 4598
+ - term: YEAR
+ period: 3
+ price:
+ currencyCode: USD
+ value: 4599
+ renewalPrice:
+ currencyCode: USD
+ value: 6897
+ - term: YEAR
+ period: 5
+ price:
+ currencyCode: USD
+ value: 9197
+ renewalPrice:
+ currencyCode: USD
+ value: 11495
+ '400':
+ $ref: '#/components/responses/400'
+ '401':
+ $ref: '#/components/responses/401'
+ '403':
+ $ref: '#/components/responses/403'
+ '422':
+ $ref: '#/components/responses/422'
+ '429':
+ $ref: '#/components/responses/429'
+ post:
+ operationId: checkAvailability
+ x-visibility: private
+ tags:
+ - Discovery
+ summary: Check availability of one or more specific domains
+ description: 'Batch controller for domain availability checking. Accepts 1–50 domain
+
+ names alongside optional check criteria (optimization mode, ISC pricing
+
+ code). Returns one Availability result per requested domain in input
+
+ order inside `{ items: [...] }`. Domains that cannot be checked carry
+
+ an `error` object on that item.
+
+
+ For a single domain, GET /check-availability (getDomainAvailability)
+
+ offers the same check semantics and Availability result without a
+
+ request body; the response is the lone item unwrapped.
+
+
+ Availability is best-effort indicative; the authoritative check is
+
+ always performed at quote time. This controller does not persist the
+
+ check — there is no check identity or poll URL.
+
+ '
+ parameters:
+ - $ref: '#/components/parameters/xRequestId'
+ - $ref: '#/components/parameters/xShopperId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AvailabilityCheckCriteria'
+ examples:
+ defaultBehavior:
+ summary: Omit optimizeFor — defaults to SPEED
+ value:
+ domains:
+ - example.com
+ - example.net
+ speedCheck:
+ summary: Check two domains using cached data
+ value:
+ domains:
+ - example.com
+ - example.net
+ optimizeFor: SPEED
+ accuracyCheck:
+ summary: Live registry check with ISC pricing context
+ value:
+ domains:
+ - example.com
+ optimizeFor: ACCURACY
+ iscCode: ISC_PARTNER_001
+ security:
+ - oauth2:
+ - domains.domain:read
+ responses:
+ '200':
+ description: 'Per-domain availability results in request order. Uncheckable items carry an error object.
+
+ '
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - items
+ properties:
+ items:
+ type: array
+ description: 'Availability results in the same order as the domains array in the request body.
+
+ '
+ items:
+ $ref: '#/components/schemas/Availability'
+ examples:
+ mixedResults:
+ summary: One available domain and one uncheckable domain
+ value:
+ items:
+ - domain: example.com
+ available: true
+ definitive: false
+ inventory: REGISTRY
+ prices:
+ - term: YEAR
+ period: 1
+ price:
+ currencyCode: USD
+ value: 1999
+ renewalPrice:
+ currencyCode: USD
+ value: 1999
+ - domain: invalid..com
+ error:
+ name: MISMATCH_FORMAT
+ correlationId: 9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c
+ message: 'does not conform to the ''domain'' format, based on pattern: /^[^.\s]{1,63}(\.[^.\s]{1,63}){1,2}$/'
+ '400':
+ $ref: '#/components/responses/400'
+ '401':
+ $ref: '#/components/responses/401'
+ '403':
+ $ref: '#/components/responses/403'
+ '422':
+ $ref: '#/components/responses/422'
+ '429':
+ $ref: '#/components/responses/429'
+ /registration-quotes:
+ post:
+ operationId: quoteDomainRegistration
+ tags:
+ - Registration Quotes
+ summary: Quote a single-domain registration (no commitment)
+ description: 'Prices the registration, resolves contact and preference settings,
+
+ returns required legal agreements, and mints a single-use quoteToken
+
+ with a 10-minute TTL. Free and read-only; safe to call speculatively.
+
+
+ When the domain is unavailable, `available: false` is returned with
+
+ no quoteToken — this is a valid non-error response.
+
+
+ When required contact fields are missing, a `422` is returned with
+
+ field-level details so the agent can collect the missing data and re-quote.
+
+ '
+ parameters:
+ - $ref: '#/components/parameters/xRequestId'
+ - $ref: '#/components/parameters/xShopperId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - domain
+ properties:
+ domain:
+ type: string
+ description: The domain name to quote, in punycode A-label form.
+ example: example.com
+ period:
+ type: integer
+ minimum: 1
+ maximum: 10
+ default: 1
+ description: Registration period in years.
+ profileId:
+ description: 'ID of a saved registration profile. Omit to fall back to the account-default profile or account
+ identity.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/uuid'
+ profile:
+ description: 'One-time inline contacts and purchase preference defaults for this quote. Not persisted.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/InlineRegistrationProfile'
+ examples:
+ minimal:
+ summary: Minimal — derive registrant from account identity
+ value:
+ domain: example.com
+ withProfile:
+ summary: With saved profile ID
+ value:
+ domain: example.com
+ period: 2
+ profileId: 14514a29-5fce-4624-8d8a-d8abd56015e2
+ security:
+ - oauth2:
+ - domains.domain:read
+ responses:
+ '200':
+ description: 'Registration quote. When available is false, no quoteToken is returned; this is not an error.
+
+ '
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RegistrationQuote'
+ '400':
+ $ref: '#/components/responses/400'
+ '401':
+ $ref: '#/components/responses/401'
+ '403':
+ $ref: '#/components/responses/403'
+ '422':
+ $ref: '#/components/responses/422'
+ '429':
+ $ref: '#/components/responses/429'
+ /registrations:
+ post:
+ operationId: registerDomain
+ tags:
+ - Registrations
+ summary: Register a domain (requires quoteToken)
+ description: 'Executes a previously quoted domain registration. **Irreversible once
+
+ accepted; creates a charge.** Requires a valid unexpired quoteToken from
+
+ `quoteDomainRegistration`, an `Idempotency-Key` header, and a consent
+
+ record. The target domain and period are in the request body alongside
+
+ the quoteToken.
+
+
+ Idempotency takes precedence over the single-use check: retrying with
+
+ the same `Idempotency-Key` replays the original operation even after
+
+ the token is consumed.
+
+
+ Returns a `Registration` entity. Poll `links[rel=self]`
+
+ (`GET /registrations/{registrationId}`) until status is `COMPLETED` or
+
+ `FAILED`. The `operationId` field is also provided for clients that
+
+ prefer `GET /operations/{operationId}`; both resolve the same resource.
+
+
+ Poll either until status is `COMPLETED` or `FAILED`. The operation is
+
+ fire-and-forget; always poll at least once even if the server completed
+
+ it synchronously.
+
+ '
+ parameters:
+ - $ref: '#/components/parameters/xRequestId'
+ - $ref: '#/components/parameters/xShopperId'
+ - $ref: '#/components/parameters/idempotencyKey'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Registration'
+ examples:
+ withToken:
+ summary: Execute with quoteToken and direct consent
+ value:
+ domain: example.com
+ period: 1
+ quoteToken: 7f3a2b1c-9d8e-4012-a5b6-c1d2e3f4a5b6
+ consent:
+ agreementTypes:
+ - DNRA
+ agreedAt: '2026-06-12T10:02:00Z'
+ agreedBy:
+ type: DIRECT
+ principal: shopper_123
+ ip: 203.0.113.7
+ security:
+ - oauth2:
+ - domains.domain:create
+ responses:
+ '202':
+ description: 'Registration accepted. Poll the self link for status.
+
+ '
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ Location:
+ $ref: '#/components/headers/location'
+ Retry-After:
+ $ref: '#/components/headers/retryAfter'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Registration'
+ '400':
+ $ref: '#/components/responses/400'
+ '401':
+ $ref: '#/components/responses/401'
+ '403':
+ $ref: '#/components/responses/403'
+ '409':
+ $ref: '#/components/responses/409'
+ '422':
+ $ref: '#/components/responses/422'
+ '429':
+ $ref: '#/components/responses/429'
+ /registrations/{registrationId}:
+ get:
+ operationId: getRegistration
+ tags:
+ - Registrations
+ summary: Get a registration record
+ description: 'Returns a single registration record by its server-assigned registrationId, including the current execution
+ status and the domain expiry date once the registration completes. This is the concrete poll endpoint for registration
+ operations; the abstract equivalent is GET /operations/{operationId}.
+
+ '
+ parameters:
+ - $ref: '#/components/parameters/xRequestId'
+ - $ref: '#/components/parameters/xShopperId'
+ - $ref: '#/components/parameters/registrationId'
+ security:
+ - oauth2:
+ - domains.domain:read
+ responses:
+ '200':
+ description: Registration record returned.
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ Retry-After:
+ $ref: '#/components/headers/retryAfter'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Registration'
+ '401':
+ $ref: '#/components/responses/401'
+ '403':
+ $ref: '#/components/responses/403'
+ '404':
+ $ref: '#/components/responses/404'
+ '429':
+ $ref: '#/components/responses/429'
+ /domain-names/{domain-name}:
+ get:
+ operationId: getDomain
+ tags:
+ - Domains
+ summary: Get a registered domain
+ description: 'Returns the management view of a single registered domain owned by the authenticated account, including
+ status, nameservers, privacy and auto-renew preferences, and expiry date.
+
+ '
+ parameters:
+ - $ref: '#/components/parameters/xRequestId'
+ - $ref: '#/components/parameters/xShopperId'
+ - $ref: '#/components/parameters/domainNamePath'
+ security:
+ - oauth2:
+ - domains.domain:read
+ responses:
+ '200':
+ description: Domain found.
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Domain'
+ '401':
+ $ref: '#/components/responses/401'
+ '403':
+ $ref: '#/components/responses/403'
+ '404':
+ $ref: '#/components/responses/404'
+ '429':
+ $ref: '#/components/responses/429'
+ /domain-names/{domain-name}/nameservers:
+ put:
+ operationId: updateNameservers
+ tags:
+ - Domain Management
+ summary: Replace the nameservers for a domain
+ description: 'Replaces the authoritative nameservers for the domain with the provided list. Minimum 2, maximum 13. Returns
+ a DomainOperation; propagation to the registry is asynchronous.
+
+ '
+ parameters:
+ - $ref: '#/components/parameters/xRequestId'
+ - $ref: '#/components/parameters/xShopperId'
+ - $ref: '#/components/parameters/domainNamePath'
+ - $ref: '#/components/parameters/idempotencyKey'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/NameServers'
+ examples:
+ nameservers:
+ value:
+ - ns1.example.com
+ - ns2.example.com
+ security:
+ - oauth2:
+ - domains.nameserver:update
+ responses:
+ '202':
+ description: Nameserver update accepted; poll the operation for completion.
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ Location:
+ $ref: '#/components/headers/location'
+ Retry-After:
+ $ref: '#/components/headers/retryAfter'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DomainOperation'
+ '400':
+ $ref: '#/components/responses/400'
+ '401':
+ $ref: '#/components/responses/401'
+ '403':
+ $ref: '#/components/responses/403'
+ '404':
+ $ref: '#/components/responses/404'
+ '422':
+ $ref: '#/components/responses/422'
+ '429':
+ $ref: '#/components/responses/429'
+ /zones/{zone}/dns-records:
+ post:
+ operationId: createDNSRecord
+ tags:
+ - Records
+ summary: Create a DNS record for a zone
+ description: 'Creates a new DNS record in the GoDaddy-managed zone. Changes are applied synchronously; no operation
+ polling required.
+
+ '
+ parameters:
+ - $ref: '#/components/parameters/xRequestId'
+ - $ref: '#/components/parameters/xShopperId'
+ - $ref: '#/components/parameters/zonePath'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DNSRecord'
+ examples:
+ aRecord:
+ summary: Create an A record
+ value:
+ name: '@'
+ type: A
+ data: 192.0.2.1
+ ttl: 3600
+ mxRecord:
+ summary: Create an MX record
+ value:
+ name: '@'
+ type: MX
+ data: mail.example.com.
+ ttl: 3600
+ priority: 10
+ security:
+ - oauth2:
+ - domains.dns:update
+ responses:
+ '201':
+ description: DNS record created.
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ Location:
+ $ref: '#/components/headers/location'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DNSRecord'
+ '400':
+ $ref: '#/components/responses/400'
+ '401':
+ $ref: '#/components/responses/401'
+ '403':
+ $ref: '#/components/responses/403'
+ '404':
+ $ref: '#/components/responses/404'
+ '409':
+ $ref: '#/components/responses/409'
+ '422':
+ $ref: '#/components/responses/422'
+ '429':
+ $ref: '#/components/responses/429'
+ /operations/{operationId}:
+ get:
+ operationId: getOperation
+ tags:
+ - Operations
+ summary: Poll an async domain operation
+ description: 'Universal poll endpoint for all asynchronous domain mutations. Returns
+
+ the current state of the operation. Non-terminal responses include a
+
+ `Retry-After` header.
+
+
+ Terminal statuses:
+
+ - `COMPLETED` — operation succeeded; `result` contains the final outcome.
+
+ - `FAILED` — operation terminated with an error; `error` contains detail.
+
+
+ While status is non-terminal (`CONFIRMED`, `EXECUTING`), neither
+
+ `result` nor `error` is present. Poll until a terminal status is reached.
+
+
+ The poll URL is provided in the `Location` header of the initiating 202
+
+ response and in `links[rel=self]`. Clients must not construct this URL
+
+ independently.
+
+ '
+ parameters:
+ - $ref: '#/components/parameters/xRequestId'
+ - $ref: '#/components/parameters/xShopperId'
+ - $ref: '#/components/parameters/operationId'
+ security:
+ - oauth2:
+ - domains.domain:read
+ responses:
+ '200':
+ description: Current operation state.
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ Retry-After:
+ $ref: '#/components/headers/retryAfter'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DomainOperation'
+ examples:
+ executing:
+ summary: Operation in progress
+ value:
+ operationId: 9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c
+ type: REGISTER
+ domain: example.com
+ status: EXECUTING
+ createdAt: '2026-06-12T10:02:05Z'
+ updatedAt: '2026-06-12T10:02:07Z'
+ links:
+ - rel: self
+ href: /v3/domains/operations/9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c
+ completed:
+ summary: Registration completed
+ value:
+ operationId: 9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c
+ type: REGISTER
+ domain: example.com
+ status: COMPLETED
+ result:
+ expiresAt: '2027-06-12T10:02:10Z'
+ orderId: ord_xyz789
+ createdAt: '2026-06-12T10:02:05Z'
+ updatedAt: '2026-06-12T10:02:10Z'
+ links:
+ - rel: self
+ href: /v3/domains/operations/9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c
+ - rel: domain
+ href: /v3/domains/domain-names/example.com
+ '401':
+ $ref: '#/components/responses/401'
+ '403':
+ $ref: '#/components/responses/403'
+ '404':
+ $ref: '#/components/responses/404'
+ '429':
+ $ref: '#/components/responses/429'
+components:
+ parameters:
+ xRequestId:
+ name: X-Request-Id
+ in: header
+ description: 'Optional client-generated request correlation identifier, propagated across services and returned in the
+ response X-Request-Id header.
+
+ '
+ required: false
+ schema:
+ type: string
+ xShopperId:
+ name: X-Shopper-Id
+ in: header
+ description: 'Reseller acting on behalf of a shopper account. When present, all domain operations are scoped to the
+ specified shopper. Absent, the authenticated entity''s own account is used. Only valid for reseller OAuth tokens.
+
+ '
+ required: false
+ schema:
+ type: string
+ example: shopper_123
+ idempotencyKey:
+ name: Idempotency-Key
+ in: header
+ description: 'Client-generated unique key (UUID recommended). Retrying a mutating request with the same Idempotency-Key
+ returns the original response without creating a duplicate side effect. Required on all execute endpoints.
+
+ '
+ required: true
+ schema:
+ type: string
+ minLength: 16
+ maxLength: 64
+ example: 9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c
+ domainNamePath:
+ name: domain-name
+ in: path
+ description: 'The domain name in punycode A-label form (e.g., example.com). For IDNs, use the punycode representation.
+
+ '
+ required: true
+ schema:
+ type: string
+ example: example.com
+ registrationId:
+ name: registrationId
+ in: path
+ description: Server-assigned registration identifier.
+ required: true
+ schema:
+ $ref: '#/components/schemas/uuid'
+ operationId:
+ name: operationId
+ in: path
+ description: 'The server-assigned operation identifier returned in the 202 response of any async domain mutation.
+
+ '
+ required: true
+ schema:
+ $ref: '#/components/schemas/uuid'
+ example: 9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c
+ zonePath:
+ name: zone
+ in: path
+ description: 'The domain name in punycode A-label form (e.g., example.com). For IDNs, use the punycode representation.
+
+ '
+ required: true
+ schema:
+ type: string
+ example: example.com
+ headers:
+ xRequestId:
+ description: Request correlation identifier echoed from the request or server-generated.
+ schema:
+ $ref: '#/components/schemas/uuid'
+ location:
+ description: URL of the created or async resource.
+ schema:
+ type: string
+ format: uri
+ retryAfter:
+ description: 'Suggested number of seconds before the client should poll again. Present on 202 responses and non-terminal
+ operation poll responses.
+
+ '
+ schema:
+ type: integer
+ example: 5
+ responses:
+ '400':
+ description: Malformed request syntax, missing required field, or invalid field type.
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/error'
+ '401':
+ description: Authentication credentials are missing or invalid.
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/error'
+ '403':
+ description: Authenticated identity is not authorized to perform this operation.
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/error'
+ '404':
+ description: The requested resource was not found.
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/error'
+ '409':
+ description: 'Conflict — the request cannot be completed in the current state. Used for quote lifecycle errors (quote_expired,
+ quote_mismatch, quote_consumed, consent_principal_mismatch) and domain state conflicts such as domain_already_exists.
+
+ '
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/error'
+ '422':
+ description: 'Semantically invalid request — valid structure but violates a business rule, such as an ineligible contact,
+ unsupported TLD, or non-renewable domain status.
+
+ '
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/error'
+ '429':
+ description: Too many requests — rate limit exceeded.
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/error'
+ '503':
+ description: Service temporarily unavailable.
+ headers:
+ X-Request-Id:
+ $ref: '#/components/headers/xRequestId'
+ Retry-After:
+ $ref: '#/components/headers/retryAfter'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/error'
+ schemas:
+ Agreement:
+ title: Agreement
+ description: 'A legal agreement that must be accepted prior to executing a domain operation. Agreements are returned
+ in the quote response and must be acknowledged in the execute request via the consent.agreementTypes array.
+
+ '
+ type: object
+ required:
+ - agreementType
+ - title
+ properties:
+ agreementType:
+ description: 'The type of legal agreement. Identifies which agreement text the customer must accept.
+
+ '
+ example: DNRA
+ allOf:
+ - $ref: '#/components/schemas/AgreementType'
+ title:
+ type: string
+ description: 'Human-readable title of the agreement, suitable for display to the customer.
+
+ '
+ example: Domain Name Registration Agreement
+ url:
+ type: string
+ format: uri
+ description: URL to the full legal text of this agreement. Present when available.
+ example: https://www.godaddy.com/agreements/showdoc?pageid=reg_sa
+ Availability:
+ title: Availability
+ description: 'The availability check result for a single requested domain. A checkable domain returns the available
+ flag plus optional pricing. A domain that could not be checked carries an error object and no availability fields.
+ Exactly one of available or error is present per item.
+
+ Availability is best-effort indicative; the authoritative availability check is performed at quote time. definitive:
+ true means the result was confirmed directly with the registry rather than from a cached zone check.
+
+ '
+ type: object
+ required:
+ - domain
+ properties:
+ domain:
+ type: string
+ description: 'The domain name checked, normalized to punycode A-label form.
+
+ '
+ example: example.com
+ unicodeDomain:
+ type: string
+ description: 'The Unicode (U-label) form of the domain. Present only for IDN domains.
+
+ '
+ example: münchen.de
+ available:
+ type: boolean
+ description: 'Whether this domain appears to be available for registration. Best-effort; re-verified at quote time.
+ Present only when the domain was successfully checked (no error).
+
+ '
+ definitive:
+ type: boolean
+ description: 'When true, the availability result was confirmed directly with the registry (ACCURACY mode). When
+ false, the result is from a cached zone data check (SPEED mode) and may be stale.
+
+ '
+ inventory:
+ description: 'The inventory source for this domain. Present when available is true and pricing fields are returned.
+
+ '
+ example: REGISTRY
+ allOf:
+ - $ref: '#/components/schemas/InventoryType'
+ prices:
+ type: array
+ description: 'Indicative pricing offered per registration term length, in ascending order of period. Present when
+ available is true. Prices are best-effort indicative and are locked only at quote time.
+
+ '
+ items:
+ $ref: '#/components/schemas/TermPrice'
+ error:
+ description: 'Present when this domain could not be checked. One of available or error is present, never both. correlationId
+ is required and should echo the X-Request-Id header value for this request.
+
+ Common name values (aligned with v2 find API error codes): MISMATCH_FORMAT — the domain name does not conform
+ to the expected format. UNSUPPORTED_TLD — the TLD is not supported for this account or check. INVALID_DOMAIN —
+ the availcheck service reported a syntax error for this domain. ERROR_UNKNOWN — the check failed and could not
+ be classified further.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/error'
+ AvailabilityCheckCriteria:
+ title: Availability Check Criteria
+ description: 'Criteria for an availability check. Specifies 1–50 domain names and optional parameters that influence
+ how the check is performed. This controller does not persist the check; there is no check identity or poll URL.
+
+ '
+ type: object
+ required:
+ - domains
+ properties:
+ domains:
+ type: array
+ minItems: 1
+ maxItems: 50
+ items:
+ type: string
+ description: 'List of 1–50 domain names to check, in punycode A-label form for IDNs.
+
+ '
+ example:
+ - example.com
+ - example.net
+ optimizeFor:
+ default: SPEED
+ description: 'Optional. When omitted, defaults to SPEED. Availability is always re-verified authoritatively at quote
+ time regardless of this setting.
+
+ '
+ example: SPEED
+ allOf:
+ - $ref: '#/components/schemas/OptimizationTarget'
+ iscCode:
+ type: string
+ description: 'Reseller ISC (International Shopper Code) for pricing context. When provided, the indicative prices
+ in the results reflect the applicable reseller rates for this ISC.
+
+ '
+ example: ISC_PARTNER_001
+ Consent:
+ title: Consent
+ description: 'Customer consent record for a domain operation, capturing which legal agreements were accepted, when,
+ and by whom. This object is self-reported by the caller and treated as supplementary attestation. The server verifies
+ agreedBy.principal against the authenticated identity (auth token + X-Shopper-Id resolution) and rejects with consent_principal_mismatch
+ on disagreement. The persisted consent record is the union of this claimed block and the verified auth context.
+
+ '
+ type: object
+ required:
+ - agreementTypes
+ - agreedAt
+ - agreedBy
+ properties:
+ agreementTypes:
+ type: array
+ minItems: 1
+ items:
+ $ref: '#/components/schemas/AgreementType'
+ description: 'The agreement types the customer accepted. Must match the agreementType values returned in the corresponding
+ quote''s requiredAgreements array.
+
+ '
+ example:
+ - DNRA
+ agreedAt:
+ description: 'The timestamp at which the principal expressed consent. Should reflect when the customer clicked accept
+ or confirmed the operation, not when the API call was made.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/date-time'
+ agreedBy:
+ $ref: '#/components/schemas/ConsentActor'
+ ConsentActor:
+ title: Consent Actor
+ description: 'Identifies who gave consent and who transmitted it. One uniform schema for all actor types. Self-reported
+ by the caller and treated as supplementary attestation; the server verifies principal against the resolved auth identity
+ (OAuth token + X-Shopper-Id) and rejects with consent_principal_mismatch on disagreement. The persisted consent record
+ is the union of this block and the verified auth context.
+
+ principal identifies the account holder whose consent is being recorded. actor identifies the automated or intermediary
+ party that transmitted the consent when different from the principal. For DIRECT, actor is omitted.
+
+ '
+ type: object
+ x-sensitivity: confidential
+ required:
+ - type
+ - principal
+ properties:
+ type:
+ description: 'Who transmitted consent relative to the principal.
+
+ '
+ example: DIRECT
+ allOf:
+ - $ref: '#/components/schemas/ConsentActorType'
+ principal:
+ type: string
+ description: 'The shopper or account ID whose consent is being recorded. Must match the shopper resolved from the
+ OAuth token and X-Shopper-Id header; mismatch returns consent_principal_mismatch.
+
+ '
+ example: shopper_123
+ actor:
+ type: string
+ description: 'The automated agent or system that transmitted the consent. Omitted for DIRECT. For AGENT, identifies
+ the specific agent instance. For RESELLER, may identify the reseller when distinct from the OAuth token subject.
+
+ '
+ example: agent:claude/atlas-1
+ ip:
+ type: string
+ description: 'The IP address of the principal at the time consent was expressed, if known. Optional — an absent
+ IP with a verified principal is preferred over a fabricated one.
+
+ '
+ example: 203.0.113.7
+ DNSRecord:
+ title: DNS Record
+ description: 'A single DNS resource record in the zone for a domain managed by GoDaddy DNS. Supports standard IANA record
+ types plus the GoDaddy ALIAS extension. SOA records are read-only and managed by GoDaddy''s authoritative DNS infrastructure.
+ The record is uniquely identified by the combination of name, type, and data.
+
+ '
+ type: object
+ required:
+ - name
+ - type
+ - data
+ - ttl
+ properties:
+ recordId:
+ type: string
+ description: 'Server-assigned stable identifier for this DNS record.
+
+ '
+ readOnly: true
+ name:
+ type: string
+ minLength: 1
+ maxLength: 255
+ description: 'The DNS record name (label), relative to the zone apex. Use @ to represent the zone apex itself. Wildcards
+ (*) are supported for A, AAAA, and CNAME records.
+
+ '
+ example: '@'
+ type:
+ description: 'The DNS resource record type. Together with name and data, uniquely identifies this record in the
+ zone. Determines the expected data format and which optional fields (priority, service, port, etc.) apply. SOA
+ and NS records are read-only.
+
+ '
+ example: A
+ allOf:
+ - $ref: '#/components/schemas/DnsRecordType'
+ data:
+ type: string
+ minLength: 1
+ maxLength: 512
+ description: 'The record data, formatted per the record type. For MX: the mail exchange hostname (e.g. "mail.example.com.").
+ Supply priority as the sibling priority field. For SRV: the target hostname; supply priority, weight, port, service,
+ and protocol as sibling fields. For TXT: the text value (quotes are handled by the DNS layer). For A/AAAA: the
+ IP address. For CNAME/ALIAS: the target hostname with trailing dot.
+
+ '
+ example: 192.0.2.1
+ ttl:
+ type: integer
+ minimum: 600
+ maximum: 86400
+ description: 'Time-to-live in seconds. Controls how long resolvers may cache this record. Minimum 600 (10 minutes);
+ maximum 86400 (24 hours).
+
+ '
+ example: 3600
+ priority:
+ type: integer
+ minimum: 0
+ maximum: 65535
+ description: 'Record priority. Required for MX and SRV records. Lower values indicate higher preference. Omit for
+ all other record types.
+
+ '
+ example: 10
+ service:
+ type: string
+ description: 'Service name for SRV and TLSA records, prefixed with an underscore (e.g. `_http`, `_smtp`). Combined
+ with the protocol to form the record name as `_service._proto.name`.
+
+ '
+ example: _http
+ port:
+ type: integer
+ minimum: 0
+ maximum: 65535
+ description: 'TCP or UDP port number for the target service. Used in SRV records to direct clients to the correct
+ port, and in TLSA records to identify the service endpoint being certified.
+
+ '
+ example: 443
+ weight:
+ type: integer
+ minimum: 0
+ maximum: 65535
+ description: 'Relative weight for load distribution among SRV records with equal priority. Higher values increase
+ the probability of selection. Use 0 when only one target exists at a given priority.
+
+ '
+ example: 10
+ protocol:
+ type: string
+ description: 'Transport protocol for SRV and TLSA records, prefixed with an underscore. Typically `_tcp` or `_udp`.
+
+ '
+ example: _tcp
+ flag:
+ type: integer
+ minimum: 0
+ maximum: 255
+ description: 'CAA record flag. Certification Authority restriction flags byte (RFC 8659; CAA only) Use 0 for non-critical,
+ 128 for critical (issuer must understand the tag).
+
+ '
+ example: 0
+ tag:
+ type: string
+ description: 'CAA record property tag. Common values: `issue` (authorize CA to issue), `issuewild` (wildcard certs),
+ `iodef` (violation reporting URL).
+
+ '
+ example: issue
+ NameServers:
+ title: Name Servers
+ description: 'Authoritative nameserver hostnames for a domain. ICANN requires a minimum of two nameservers; registries
+ accept up to thirteen.
+
+ '
+ type: array
+ minItems: 2
+ maxItems: 13
+ items:
+ $ref: '#/components/schemas/NameserverHostname'
+ NameserverHostname:
+ title: Nameserver Hostname
+ description: 'A fully-qualified nameserver hostname in punycode A-label form. Labels are dot-separated; a trailing dot
+ is not required.
+
+ '
+ type: string
+ minLength: 1
+ maxLength: 253
+ Domain:
+ title: Domain
+ description: 'A registered domain owned by the authenticated account. Represents the full management view of a domain:
+ registration metadata, lifecycle status, nameservers, privacy and auto-renew preferences.
+
+ '
+ type: object
+ required:
+ - domain
+ - status
+ - expiresAt
+ - createdAt
+ - autoRenew
+ - privacy
+ properties:
+ domain:
+ type: string
+ description: 'The registered domain name in punycode A-label form. For IDNs, the Unicode (U-label) form is available
+ in the idnDomain field.
+
+ '
+ readOnly: true
+ example: example.com
+ idnDomain:
+ type: string
+ description: 'The Unicode (U-label) representation of the domain. Present only for internationalized domain names
+ (IDNs).
+
+ '
+ readOnly: true
+ status:
+ description: 'The current lifecycle status of this domain.
+
+ '
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/DomainStatus'
+ expiresAt:
+ description: 'The date and time when this domain registration expires. After expiry the domain enters a grace period
+ before deletion.
+
+ '
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/date-time'
+ createdAt:
+ description: 'The date and time when this domain was first registered with GoDaddy.
+
+ '
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/date-time'
+ renewBy:
+ description: 'The date and time when this domain must renew on before entering expiry.
+
+ '
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/date-time'
+ updatedAt:
+ description: 'The date and time when this domain was last updated
+
+ '
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/date-time'
+ autoRenew:
+ type: boolean
+ description: 'Whether this domain is set to auto-renew before expiry. When true, the registration is renewed automatically
+ before it expires.
+
+ '
+ privacy:
+ type: boolean
+ description: 'Whether WHOIS privacy protection is active for this domain. When true, the registrant''s contact details
+ are masked in public WHOIS.
+
+ '
+ transferLock:
+ type: boolean
+ description: 'Whether the transfer lock (registrar lock / EPP lock) is active. When true, outbound transfers to
+ another registrar are blocked.
+
+ '
+ readOnly: true
+ nameServers:
+ type: array
+ minItems: 2
+ maxItems: 13
+ items:
+ type: string
+ description: 'The current authoritative nameservers for this domain.
+
+ '
+ example:
+ - ns01.domaincontrol.com
+ - ns02.domaincontrol.com
+ allOf:
+ - $ref: '#/components/schemas/NameServers'
+ links:
+ type: array
+ items:
+ $ref: '#/components/schemas/link-description'
+ description: 'HATEOAS link relations for this domain. rel=self — the canonical URL for this domain record. rel=contacts
+ — the domain''s WHOIS contact records. rel=nameservers — the domain''s authoritative nameservers. rel=dns-records
+ — the domain''s DNS records managed by GoDaddy.
+
+ '
+ readOnly: true
+ DomainOperation:
+ title: Domain Operation
+ description: "The abstract operation envelope for all domain mutations, returned by the universal GET /operations/{operationId}\
+ \ endpoint. Concrete specializations — Registration, Renewal, and Transfer — are returned directly by their respective\
+ \ POST endpoints and carry the same operationId. Developers who do not need the abstract view can poll the concrete\
+ \ resource (GET /registrations/{id}, etc.) and ignore this type entirely.\nOperation IDs are unique across all concrete\
+ \ types, so either poll path works for any given operation.\nAsync state machine:\n status tracks where the operation\
+ \ is in its lifecycle. Non-terminal values\n (CONFIRMED, EXECUTING) are transient — poll until a terminal value is\
+ \ reached.\n result and error are mutually exclusive terminal payloads:\n COMPLETED — operation succeeded; result\
+ \ contains the final outcome data.\n FAILED — operation terminated; error contains failure detail.\n Neither result\
+ \ nor error is present while status is non-terminal.\n"
+ type: object
+ required:
+ - operationId
+ - type
+ - status
+ properties:
+ operationId:
+ description: 'Stable, server-assigned identifier for this operation. Unique across all operation types. Use to poll
+ GET /operations/{operationId}. Matches the operationId on the corresponding concrete resource (e.g. Registration).
+
+ '
+ readOnly: true
+ example: 9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c
+ allOf:
+ - $ref: '#/components/schemas/uuid'
+ type:
+ description: 'The type of operation being tracked. Determines which fields appear in result on COMPLETED and the
+ concrete resource collection (REGISTER → /registrations, RENEW → /renewals, TRANSFER_IN → /transfers).
+
+ '
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/DomainOperationType'
+ domain:
+ type: string
+ description: The domain name this operation applies to.
+ readOnly: true
+ example: example.com
+ status:
+ description: 'Current position in the operation lifecycle. Poll until COMPLETED or FAILED. Non-terminal while CONFIRMED
+ or EXECUTING; terminal values are mutually exclusive with each other and do not revert.
+
+ '
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/DomainOperationStatus'
+ result:
+ description: 'Present when status is COMPLETED. Absent while non-terminal and when status is FAILED.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/DomainOperationResult'
+ readOnly: true
+ error:
+ description: 'Present when status is FAILED. Absent while non-terminal and when status is COMPLETED.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/error'
+ readOnly: true
+ links:
+ type: array
+ items:
+ $ref: '#/components/schemas/link-description'
+ description: 'HATEOAS link relations for this operation. rel=self — the canonical URL for this abstract operation
+ view. rel=registration, rel=renewal, or rel=transfer — the same resource viewed through its concrete typed collection.
+ rel=domain — the domain-name resource affected by this operation.
+
+ '
+ readOnly: true
+ createdAt:
+ description: Timestamp when this operation was created.
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/date-time'
+ updatedAt:
+ description: Timestamp of the most recent status update.
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/date-time'
+ DomainOperationResult:
+ title: Domain Operation Result
+ description: 'The terminal success payload for a completed domain operation. Returned on the parent DomainOperation
+ when status is COMPLETED. Absent for non-terminal statuses (CONFIRMED, EXECUTING) and for FAILED operations.
+
+ Once status reaches COMPLETED it is terminal: result is populated, remains available on subsequent polls, and status
+ does not revert. Interpret the fields present in result using the parent operation''s type:
+
+ REGISTER — expiresAt, orderId.
+
+ '
+ type: object
+ readOnly: true
+ properties:
+ expiresAt:
+ description: 'New domain expiry date. Present for REGISTER and RENEW operations.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/date-time'
+ orderId:
+ type: string
+ description: 'The commerce order ID associated with the charge. Present for commercial operations (REGISTER, RENEW,
+ TRANSFER_IN).
+
+ '
+ example: ord_abc123
+ updatedAt:
+ description: 'Timestamp of the completed non-commercial mutation.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/date-time'
+ Error:
+ $ref: '#/components/schemas/error'
+ Registration:
+ title: Registration
+ description: 'A domain registration entity created when a POST /registrations request is accepted. Registrations are
+ a top-level resource with their own stable registrationId; the domain relationship is captured in the representation.
+
+ On POST /registrations, supply the writable fields (domain, period, quoteToken, consent, and optionally profileId/profile).
+ The server returns the full Registration with readOnly fields populated. Poll links[rel=self] until status reaches
+ COMPLETED or FAILED. The same resource is also reachable via GET /operations/{operationId} for clients operating at
+ the abstract level; operationId is included in the representation for that purpose.
+
+ '
+ type: object
+ required:
+ - domain
+ - quoteToken
+ - consent
+ properties:
+ registrationId:
+ description: 'Server-assigned stable identifier for this registration record.
+
+ '
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/uuid'
+ domain:
+ type: string
+ description: 'The domain name to register, in punycode A-label form for IDNs. Must match the domain in the quoteToken.
+
+ '
+ example: example.com
+ period:
+ type: integer
+ minimum: 1
+ maximum: 10
+ default: 1
+ description: Registration period in years. Must match the period in the quote.
+ example: 1
+ profileId:
+ description: 'ID of a saved registration profile to use for contacts and preference defaults. Omit to fall back
+ to the account-default profile for the domain''s TLD, then to account identity.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/uuid'
+ profile:
+ description: 'One-time inline contacts and purchase preference defaults for this registration. Not persisted. Must
+ match the profile supplied on the quote when a profile was included at quote time.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/InlineRegistrationProfile'
+ quoteToken:
+ description: 'The single-use opaque token from the preceding quoteDomainRegistration call. Required. Consumed on
+ first successful execution; idempotent retries with the same Idempotency-Key replay the original operation without
+ re-consuming.
+
+ '
+ writeOnly: true
+ example: 7f3a2b1c-9d8e-4012-a5b6-c1d2e3f4a5b6
+ allOf:
+ - $ref: '#/components/schemas/uuid'
+ consent:
+ description: 'The customer''s consent record for the legal agreements returned in the quote. Must reference the
+ same agreementTypes as the quote.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/Consent'
+ status:
+ description: Current execution status of this registration.
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/DomainOperationStatus'
+ operationId:
+ description: 'Identifier of the DomainOperation tracking this registration. Same value returned by GET /operations/{operationId}
+ for abstract operation tracking.
+
+ '
+ readOnly: true
+ example: 9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c
+ allOf:
+ - $ref: '#/components/schemas/uuid'
+ expiresAt:
+ description: 'The domain''s expiry date once the registration completes. Present only when status is COMPLETED.
+
+ '
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/date-time'
+ createdAt:
+ description: Timestamp when this registration was initiated.
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/date-time'
+ updatedAt:
+ description: Timestamp of the most recent status update.
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/date-time'
+ links:
+ type: array
+ items:
+ $ref: '#/components/schemas/link-description'
+ description: 'HATEOAS link relations for this registration. rel=self — the canonical URL for this registration record.
+ rel=domain — the registered domain-name resource once the registration is complete.
+
+ '
+ readOnly: true
+ InlineRegistrationProfile:
+ title: Inline Registration Profile
+ description: 'A one-time, non-persisted set of contacts and purchase preference defaults supplied inline on a quote
+ or execute request. Use to provide registration data for this transaction without creating or updating a saved registration
+ profile.
+
+ Shared by the registration quote and execute request bodies. Every field is optional. Omitted fields account identity
+ or other default values. Provided fields override only what is supplied — contact roles replace as a whole block;
+ preference fields replace individually.
+
+ This is not a saved registration profile and is not JSON Patch. Data here applies only to the current quote or registration
+ request.
+
+ '
+ type: object
+ properties:
+ contacts:
+ description: 'Contact records for this request. Each role provided replaces that role from the resolved saved profile
+ or account identity. Omitted roles continue to resolve from the saved profile or cascade from registrant.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/Contacts'
+ autoRenew:
+ type: boolean
+ description: 'Auto-renew preference for this registration. Omit to inherit from the resolved saved profile or account
+ defaults.
+
+ '
+ privacy:
+ type: boolean
+ description: 'WHOIS privacy preference for this registration. Omit to inherit from the resolved saved profile or
+ account defaults.
+
+ '
+ nameServers:
+ description: 'Authoritative nameservers for this registration. Omit to inherit from the resolved saved profile or
+ platform defaults.
+
+ '
+ example:
+ - ns1.example.com
+ - ns2.example.com
+ allOf:
+ - $ref: '#/components/schemas/NameServers'
+ RegistrationQuote:
+ title: Registration Quote
+ description: 'A price quote for registering a single domain. Contains a locked price, resolved contact and preference
+ settings, required legal agreements, and a short-lived single-use quoteToken that must be presented on the subsequent
+ registration execute call. Execution without a valid quoteToken is structurally impossible.
+
+ When available is false, no quoteToken is returned — this is not an error; it means the domain cannot be registered
+ as requested.
+
+ '
+ type: object
+ required:
+ - domain
+ - available
+ properties:
+ quoteToken:
+ description: 'Opaque, single-use token with a 10-minute TTL. References the locked price, a hash of this request
+ body, and a hash of the resolved profile values. Absent when available is false. Treat as a capability; do not
+ parse.
+
+ '
+ example: 7f3a2b1c-9d8e-4012-a5b6-c1d2e3f4a5b6
+ allOf:
+ - $ref: '#/components/schemas/uuid'
+ expiresAt:
+ description: 'The expiry timestamp of the quoteToken. After this time, presenting the token on execute returns quote_expired
+ and a new quote must be obtained.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/date-time'
+ domain:
+ type: string
+ description: 'The domain name being quoted, in punycode A-label form for IDNs.
+
+ '
+ example: example.com
+ available:
+ type: boolean
+ description: 'Whether the domain is available for registration. When false, no quoteToken is returned. The availability
+ check at quote time is authoritative; a name sniped between suggest/availability and quote fails cleanly here.
+
+ '
+ price:
+ description: 'The locked registration price for the quoted period. Held for the duration of the quoteToken''s TTL.
+ Represents the total amount that will be charged on execute.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/simple-money'
+ renewalPrice:
+ description: 'Indicative renewal cost at current rates. Not a price guarantee; renewal pricing is locked at time
+ of renewal quote.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/simple-money'
+ period:
+ type: integer
+ minimum: 1
+ maximum: 10
+ description: Registration period in years for which the price is quoted.
+ example: 1
+ resolved:
+ description: 'The effective contact and preference settings that will be applied on execute. Review before execute
+ to verify registrant, contacts, and preferences match intent.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/ResolvedSettings'
+ requiredAgreements:
+ type: array
+ items:
+ $ref: '#/components/schemas/Agreement'
+ description: 'Legal agreements that must be accepted before executing this quote. The agreementType values from
+ this list must be included in the execute request''s consent object.
+
+ '
+ irreversible:
+ type: boolean
+ description: 'Whether executing this quote is irreversible once accepted. Use to calibrate the explicitness of any
+ confirmation step presented before execute.
+
+ '
+ example: false
+ ResolvedSettings:
+ title: Resolved Settings
+ description: 'A preview of the effective settings that will be applied if the associated quote is executed. Returned
+ in the quote response to eliminate invisible side effects — the caller sees exactly whose contact info and which preferences
+ will be used before making a commitment. contactSource names where the registrant contact came from so an agent can
+ be explicit at the confirmation step.
+
+ '
+ type: object
+ properties:
+ profileId:
+ description: 'The saved registration profile that was applied, if any. Absent when the registrant was derived from
+ account identity.
+
+ '
+ readOnly: true
+ example: 14514a29-5fce-4624-8d8a-d8abd56015e2
+ allOf:
+ - $ref: '#/components/schemas/uuid'
+ contactSource:
+ description: 'Indicates where the resolved registrant contact came from. When ACCOUNT, the agent should surface
+ this clearly to the customer before confirmation.
+
+ '
+ example: PROFILE
+ allOf:
+ - $ref: '#/components/schemas/ContactSource'
+ registrantSummary:
+ type: string
+ description: 'A human-readable one-line summary of the resolved registrant, suitable for display in a confirmation
+ prompt. When contactSource is ACCOUNT, the summary is suffixed with "(account identity)" to make the derivation
+ explicit.
+
+ '
+ x-sensitivity: confidential
+ example: Jane Smith / jane@example.com
+ autoRenew:
+ type: boolean
+ description: 'The effective auto-renew setting that will be applied upon registration.
+
+ '
+ privacy:
+ type: boolean
+ description: 'The effective WHOIS privacy setting that will be applied upon registration.
+
+ '
+ nameServers:
+ description: 'The effective nameservers that will be provisioned for the domain.
+
+ '
+ example:
+ - ns01.domaincontrol.com
+ - ns02.domaincontrol.com
+ allOf:
+ - $ref: '#/components/schemas/NameServers'
+ Suggestion:
+ title: Suggestion
+ description: 'A single available domain suggestion returned by the suggest endpoint. Availability is implied by presence
+ in the results (available-only contract) and is best-effort — the availability check at quote time is the authoritative
+ re-check. Indicative pricing may be stale; the locked price is established at quote time only.
+
+ '
+ type: object
+ required:
+ - domain
+ properties:
+ domain:
+ type: string
+ description: 'The suggested domain name in punycode A-label form.
+
+ '
+ example: sunrisebakery.com
+ listPrice:
+ description: 'Indicative undiscounted list price for a one-year registration. Promotional pricing and the final
+ locked price resolve at quote time.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/simple-money'
+ renewalPrice:
+ description: 'Indicative renewal price at current rates. Not a guarantee.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/simple-money'
+ inventory:
+ description: 'The inventory source for this domain. Present when pricing fields are present.
+
+ '
+ example: REGISTRY
+ allOf:
+ - $ref: '#/components/schemas/InventoryType'
+ TermPrice:
+ title: Term Price
+ description: 'Pricing for one registration term, including sale and list prices for the full term and optional renewal
+ and first-term breakdowns.
+
+ '
+ type: object
+ required:
+ - term
+ - period
+ - price
+ properties:
+ term:
+ description: 'Unit in which period is expressed. Currently only YEAR is supported.
+
+ '
+ example: YEAR
+ allOf:
+ - $ref: '#/components/schemas/Term'
+ period:
+ type: integer
+ minimum: 1
+ maximum: 10
+ description: 'The registration period length, in units of term.
+
+ '
+ example: 2
+ price:
+ description: 'The price for the full term covered by this price block — the current registration price for `period`
+ units of term. Discounts or promotions may be reflected in this price.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/simple-money'
+ renewalPrice:
+ description: 'Sale price that will apply when the domain renews, for the same term and period. Not a guarantee.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/simple-money'
+ uuid:
+ description: A universally unique identifier (UUID) in [RFC-4122 format](https://tools.ietf.org/html/rfc4122).
+ type: string
+ format: uuid
+ SuggestionSource:
+ title: Suggestion Source
+ description: 'A suggestion source strategy that generates domain name variations. EXTENSION — vary the TLD. KEYWORD_SPIN
+ — rotate keywords. CC_TLD — vary using country-code TLDs. PREMIUM — include premium-priced variations.
+
+ '
+ type: string
+ enum:
+ - CC_TLD
+ - EXTENSION
+ - KEYWORD_SPIN
+ - PREMIUM
+ currency-code:
+ type: string
+ title: Currency Code
+ description: A three-character ISO-4217 currency code.
+ minLength: 3
+ maxLength: 3
+ simple-money:
+ type: object
+ title: Simple Money
+ description: The currency and amount for a financial transaction, such as a balance or payment due. Use for value representations
+ with default transactable-value precision.
+ properties:
+ currencyCode:
+ $ref: '#/components/schemas/currency-code'
+ value:
+ type: integer
+ format: int64
+ description: The value, which might represent intergrals for currencies like `JPY` that are not typically fractional;
+ or, with an implied decimal fraction for currencies like `TND` that are subdivided into thousandths. For the implied
+ number of decimal places for a currency code, see [ISO-4217 Currency Codes](https://en.wikipedia.org/wiki/ISO_4217).
+ required:
+ - currencyCode
+ - value
+ InventoryType:
+ title: Inventory Type
+ description: 'The inventory source for a domain name. REGISTRY — standard registry price inventory. REGISTRY_PREMIUM
+ — registry premium tier pricing. PREMIUM — third-party premium domain marketplace.
+
+ '
+ type: string
+ enum:
+ - REGISTRY
+ - REGISTRY_PREMIUM
+ - PREMIUM
+ error-details:
+ title: Error Details
+ type: object
+ description: The error details. Required for client-side `4XX` errors.
+ properties:
+ field:
+ type: string
+ description: 'The field that caused the error. If the field is in the body, set this value to the JSON pointer to
+ that field. Required for client-side errors. When the offending value was resolved on the caller''s behalf and
+ has no request location, this is a source-scoped reference instead of a JSON pointer: "shopper:" (from
+ the account identity) or "profile:" (from a saved registration profile).'
+ value:
+ type: string
+ description: The value of the field that caused the error.
+ location:
+ type: string
+ description: The location of the field that caused the error. Value is `body`, `path`, or `query`.
+ default: body
+ issue:
+ type: string
+ description: The unique fine-grained application-level error code.
+ description:
+ type: string
+ description: The human-readable description for an issue. The description MAY change over the lifetime of an API,
+ so clients MUST NOT depend on this value.
+ required:
+ - issue
+ link-description:
+ title: Link Description
+ type: object
+ description: A request-related [HATEOAS link](https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-hyperschema-02).
+ properties:
+ href:
+ description: The complete target URL, or link, to use in combination with the method to make the related call, as
+ defined by [RFC 6570 - URI Template](https://tools.ietf.org/html/rfc6570), with the addition of the `$`, `(`,
+ and `)` characters for pre-processing. The `href` is the key HATEOAS component that links a completed call with
+ a subsequent call.
+ type: string
+ format: uri
+ rel:
+ description: The [link relation type](https://tools.ietf.org/html/rfc5988#section-4), which is an identifier for
+ a link that unambiguously describes the semantics of the link. For values, see [Link Relationship Types](https://www.iana.org/assignments/link-relations/link-relations.xhtml).
+ type: string
+ title:
+ description: The link title.
+ type: string
+ targetMediaType:
+ description: The [RFC 2046-defined media type](https://www.ietf.org/rfc/rfc2046.txt) that describes the link target.
+ type: string
+ targetSchema:
+ description: The schema that describes the link target.
+ method:
+ description: The method to use to request the link target. For example, for HTTP, this might be `GET` or `DELETE`.
+ type: string
+ submissionMediaType:
+ description: The media type with which to submit data with the request.
+ type: string
+ default: application/json
+ submissionSchema:
+ description: The schema that describes the request data.
+ required:
+ - rel
+ - href
+ error:
+ type: object
+ title: Error
+ description: The error information.
+ properties:
+ name:
+ type: string
+ description: The human-readable, unique name of the error.
+ correlationId:
+ type: string
+ description: Internal identifier used for correlation purposes.
+ message:
+ type: string
+ description: The message that describes the error.
+ informationLink:
+ type: string
+ description: The URI for detailed information related to this error for the developer.
+ details:
+ type: array
+ description: An array of additional details about the error. Required for client-side `4XX` errors.
+ additionalItems: false
+ items:
+ $ref: '#/components/schemas/error-details'
+ links:
+ type: array
+ description: An array of error-related HATEOAS links.
+ readOnly: true
+ items:
+ $ref: '#/components/schemas/link-description'
+ readOnly: true
+ required:
+ - name
+ - correlationId
+ - message
+ OptimizationTarget:
+ title: Optimization Target
+ description: 'How an availability check should prioritize speed vs. authoritative accuracy. SPEED — use cached zone
+ data for a fast response (may be slightly stale). ACCURACY — perform a live registry check for authoritative availability
+ (higher latency).
+
+ '
+ type: string
+ enum:
+ - SPEED
+ - ACCURACY
+ Term:
+ title: Term
+ description: 'The unit of measure for a registration period. YEAR — registration period expressed in whole years.
+
+ '
+ type: string
+ enum:
+ - YEAR
+ default: YEAR
+ email-address:
+ description: 'A valid, internationalized email address. Note: Up to 64 characters are allowed before and 255 characters
+ are allowed after the @ sign. However, the generally accepted maximum length for an email address is 254 characters.
+ The pattern verifies that an unquoted @ sign exists.'
+ type: string
+ minLength: 3
+ maxLength: 254
+ pattern: ^.+@[^"\-].+$
+ phone:
+ type: object
+ title: Phone
+ description: The phone number, in its canonical international [E.164 numbering plan format](https://www.itu.int/rec/T-REC-E.164/en).
+ properties:
+ countryCode:
+ type: string
+ description: The country calling code (CC), in its canonical international [E.164 numbering plan format](https://www.itu.int/rec/T-REC-E.164/en).
+ The combined length of the CC and the national number must not be greater than 15 digits. The national number
+ consists of a national destination code (NDC) and subscriber number (SN).
+ minLength: 1
+ maxLength: 3
+ pattern: ^[0-9]{1,3}?$
+ nationalNumber:
+ type: string
+ description: The national number, in its canonical international [E.164 numbering plan format](https://www.itu.int/rec/T-REC-E.164/en).
+ The combined length of the country calling code (CC) and the national number must not be greater than 15 digits.
+ The national number consists of a national destination code (NDC) and subscriber number (SN).
+ minLength: 1
+ maxLength: 14
+ pattern: ^[0-9]{1,14}?$
+ extensionNumber:
+ type: string
+ description: The extension number.
+ minLength: 1
+ maxLength: 15
+ pattern: ^[0-9]{1,15}?$
+ required:
+ - countryCode
+ - nationalNumber
+ country-code:
+ description: A two-character ISO 3166-1 code that identifies the country or region.
+ type: string
+ maxLength: 2
+ minLength: 2
+ pattern: ^([A-Z]{2}|C2)$
+ simple-address:
+ type: object
+ title: Simple Postal Address (Coarse-Grained)
+ description: Simple postal address with coarse-grained fields. Do not use for international postal addresses. Use for
+ backward compatibility only. Address does not contain a phone number.
+ properties:
+ line1:
+ type: string
+ description: The first line of the address. For example, number or street.
+ maxLength: 300
+ line2:
+ type: string
+ description: The second line of the address. For example, suite or apartment number.
+ maxLength: 300
+ city:
+ type: string
+ description: The city name.
+ maxLength: 300
+ state:
+ type: string
+ description: The [code](https://about.usps.com/who/profile/history/state-abbreviations.htm) for a US state or the
+ equivalent for other countries.
+ maxLength: 300
+ countryCode:
+ $ref: '#/components/schemas/country-code'
+ description: 'The [two-character ISO 3166-1 code](https://en.wikipedia.org/wiki/ISO_3166-1) that identifies the
+ country or region. Note: The country code for Great Britain is `GB` and not `UK` as used in the top-level domain
+ names for that country. Use country code `C2` for China for comparable uncontrolled price (CUP) method, bank-card,
+ and cross-border transactions.'
+ postalCode:
+ type: string
+ description: The postal code, which is the zip code or equivalent. Typically required for countries that have a
+ postal code or an equivalent. See [Postal Code](https://en.wikipedia.org/wiki/Postal_code).
+ maxLength: 60
+ required:
+ - line1
+ - city
+ - countryCode
+ Contact:
+ title: Contact
+ description: 'An ICANN-required contact record for a domain registration. Covers registrant, administrative, technical,
+ and billing roles. Identity fields are validated at registration-profile save time.
+
+ '
+ x-sensitivity: confidential
+ type: object
+ required:
+ - firstName
+ - lastName
+ - email
+ - phone
+ - address
+ properties:
+ firstName:
+ type: string
+ minLength: 1
+ maxLength: 60
+ description: The contact's first (given) name.
+ x-sensitivity: confidential
+ example: Jane
+ lastName:
+ type: string
+ minLength: 1
+ maxLength: 60
+ description: The contact's last (family) name.
+ x-sensitivity: confidential
+ example: Smith
+ organization:
+ type: string
+ maxLength: 100
+ description: 'Organization or company name. Required for contacts acting on behalf of a legal entity. Leave blank
+ for individual registrants.
+
+ '
+ x-sensitivity: confidential
+ example: Example LLC
+ email:
+ example: foo@bar.com
+ description: 'The contact''s email address. Used for registry WHOIS and renewal notifications.
+
+ '
+ x-sensitivity: confidential
+ allOf:
+ - $ref: '#/components/schemas/email-address'
+ phone:
+ description: 'The contact''s phone number in ITU E.164 format with GoDaddy extension notation: +{country-code}.{local-number},
+ e.g. +1.4805551234. Required by ICANN for all contact roles.
+
+ '
+ x-sensitivity: confidential
+ allOf:
+ - $ref: '#/components/schemas/phone'
+ address:
+ description: 'The contact''s mailing address for WHOIS and ICANN records.
+
+ '
+ x-sensitivity: confidential
+ allOf:
+ - $ref: '#/components/schemas/simple-address'
+ Contacts:
+ title: Contacts
+ description: 'The set of ICANN-required contact roles for a domain registration. Registrant is required; admin, tech,
+ and billing cascade from the registrant when omitted. Merge rule across resolution layers (saved profile, inline registration
+ profile): identity fields replace as a whole block per role.
+
+ '
+ x-sensitivity: confidential
+ type: object
+ required:
+ - registrant
+ properties:
+ registrant:
+ description: 'The legal owner of the domain. Required. The registrant''s identity is the authoritative WHOIS record
+ and is bound by ICANN registration agreements.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/Contact'
+ admin:
+ description: 'The administrative contact, responsible for managing the domain on behalf of the registrant. Cascades
+ from registrant when omitted.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/Contact'
+ tech:
+ description: 'The technical contact, responsible for DNS and nameserver configuration. Cascades from registrant
+ when omitted.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/Contact'
+ billing:
+ description: 'The billing contact, receives invoices and renewal notices. Cascades from registrant when omitted.
+
+ '
+ allOf:
+ - $ref: '#/components/schemas/Contact'
+ date-time:
+ description: 'A date and time, in [Internet date and time format](https://tools.ietf.org/html/rfc3339#section-5.6).
+ Note: The regular expression provides static schematic guidance but does not reject all invalid dates.'
+ type: string
+ minLength: 20
+ maxLength: 64
+ pattern: ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])[T,t]([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)([.][0-9]+)?([Zz]|[+-][0-9]{2}:[0-9]{2})$
+ ContactSource:
+ title: Contact Source
+ description: 'Where the resolved registrant contact came from. INLINE — contact was supplied via an inline registration
+ profile on the request. PROFILE — contact was resolved from a named or default saved profile. ACCOUNT — contact was
+ derived from the authenticated principal''s account identity (no profile supplied or on file).
+
+ '
+ type: string
+ enum:
+ - INLINE
+ - PROFILE
+ - ACCOUNT
+ AgreementType:
+ title: Agreement Type
+ description: 'The type of legal agreement that must be accepted prior to executing a domain operation. Additional agreement
+ types may be returned for specific TLDs or product combinations. DNRA — Domain Name Registration Agreement. DNTA —
+ Domain Name Transfer Agreement. DNPA — Domain Name Privacy Agreement. HTTPS_NOTICE — HTTPS notice acknowledgment for
+ eligible TLDs. AURA — AU Domain Agreement for .au TLD registrations and transfers. CIRA — Canadian Internet Registration
+ Authority Agreement for .ca TLD registrations and transfers.
+
+ '
+ type: string
+ ConsentActorType:
+ title: Consent Actor Type
+ description: 'Who transmitted consent on behalf of the principal. DIRECT — the principal acted directly; actor is omitted.
+ AGENT — an AI or automation acted on behalf of the principal. RESELLER — a reseller acted on behalf of a shopper.
+
+ '
+ type: string
+ DomainStatus:
+ title: Domain Status
+ description: 'The lifecycle status of a registered domain. Reflects the domain''s current operational state within the registry
+ and GoDaddy''s management layer. ACTIVE — domain is registered and is active. CANCELLED — domain has been cancelled
+ by the user or system, and is not reclaimable. DELETED_REDEEMABLE — domain is in ICANN redemption grace period; recovery
+ fees apply. EXPIRED — registration period has ended; domain is pending deletion or redemption. FAILED - domain registration
+ or transfer error. HELD_REGISTRAR - domain is held at the registrar and cannot be transferred or modified - this is usually
+ the result of a dispute. LOCKED_REGISTRAR — domain is locked at the registrar - this is usually the result of spam,
+ abuse, etc. OWNERSHIP_CHANGED - domain has been moved to another account. PARKED - domain has been parked. PENDING_REGISTRATION
+ - domain is pending setup at the registry. PENDING_TRANSFER — an outbound transfer to another registrar is in progress.
+ REPOSSESSED - domain has been confiscated - this is usually the result of a chargeback, fraud, abuse, etc. SUSPENDED
+ — domain has been administratively suspended by the registry or registrar. TRANSFERRED - domain has been transferred
+ to another registrar.
+
+ '
+ type: string
+ enum:
+ - ACTIVE
+ - CANCELLED
+ - DELETED_REDEEMABLE
+ - EXPIRED
+ - FAILED
+ - HELD_REGISTRAR
+ - LOCKED_REGISTRAR
+ - OWNERSHIP_CHANGED
+ - PARKED
+ - PENDING_REGISTRATION
+ - PENDING_TRANSFER
+ - REPOSSESSED
+ - SUSPENDED
+ - TRANSFERRED
+ DomainOperationStatus:
+ title: Domain Operation Status
+ description: 'The execution state of an asynchronous domain operation. CONFIRMED — operation has been accepted and is
+ queued for execution. EXECUTING — operation is actively being processed by the registry or downstream systems. COMPLETED
+ — operation finished successfully; result data is available. FAILED — operation terminated with an unrecoverable error;
+ error detail is attached.
+
+ '
+ type: string
+ DomainOperationType:
+ title: Domain Operation Type
+ description: 'The type of asynchronous domain operation. Used to distinguish which workflow is being polled on the /operations/{operationId}
+ endpoint. REGISTER — new domain registration.
+
+ '
+ type: string
+ DnsRecordType:
+ title: DNS Record Type
+ description: 'The type of a DNS resource record. Values correspond to IANA-assigned DNS record type mnemonics. A — IPv4
+ address record. AAAA — IPv6 address record. CAA — certification authority authorization record. CNAME — canonical
+ name alias record. MX — mail exchange record; data is the mail exchange hostname, priority is the sibling priority
+ field. NS — nameserver delegation record; read-only. SOA — start of authority record; managed by the registry; read-only.
+ SRV — service locator record. TXT — free-form text; used for SPF, DKIM, DMARC, and domain verification.
+
+ '
+ type: string
+ securitySchemes:
+ oauth2:
+ type: oauth2
+ description: 'GoDaddy OAuth 2.0 access token. The scope(s) listed on each operation are enforced per-operation. A token
+ requires at least one of the scopes listed for that operation.
+
+ '
+ flows:
+ authorizationCode:
+ authorizationUrl: https://api.godaddy.com/v2/oauth2/authorize
+ tokenUrl: https://api.godaddy.com/v2/oauth2/token
+ scopes:
+ domains.domain:read: 'Read domain records, availability, suggestions, quotes, and operations.
+
+ '
+ domains.domain:create: 'Register domains. Requires a prior quoteToken.
+
+ '
+ domains.nameserver:update: 'Replace authoritative nameservers for a domain.
+
+ '
+ domains.dns:update: 'Create, update, and delete DNS zone records.
+
+ '
diff --git a/rust/domains-client/scripts/merge-spec.py b/rust/domains-client/scripts/merge-spec.py
new file mode 100644
index 0000000..b92f61e
--- /dev/null
+++ b/rust/domains-client/scripts/merge-spec.py
@@ -0,0 +1,224 @@
+#!/usr/bin/env python3
+"""Merge the retained-v1 OpenAPI 3.0 spec into the vendored v3 spec, producing
+the single `domains.oas3.json` the crate's build.rs feeds to progenitor.
+
+Invoked by `regenerate-spec.sh` as:
+
+ python3 merge-spec.py
+
+The CLI's `domain`/`dns` commands talk to two API generations at once: v3 (the
+Domain Lifecycle Management API) for availability/suggest/get/quote/register and
+single DNS-record creation, and v1 for the handful of operations v3 does not yet
+serve (list, agreements, DNS record list/set/delete). We generate ONE progenitor
+client over ONE host base URL by merging both into a single OAS3 document:
+
+ * **v3 is the base.** Its server is `https://api.{env}.com/v3/domains`, so we
+ rewrite every v3 path to the absolute `/v3/domains/...` form and pin the
+ document `servers` to the bare host. One `base_url` (the host) then serves
+ both `/v3/domains/...` and `/v1/domains/...` requests.
+ * **v1 is injected with a `V1` name prefix.** v1 and v3 both define `DNSRecord`,
+ `DnsRecordType`, `DomainStatus`, … with different shapes, so every v1
+ component is renamed `V1` (and all its `$ref`s rewritten) before the
+ merge — v3 keeps the clean, go-forward names.
+
+Both sides are reduced to their 2xx responses (errors surface via HTTP status +
+body, read by the CLI's `api_error`), and `date-time`/`uuid` string formats are
+dropped so the generated crate needs neither chrono nor uuid.
+"""
+
+import json
+import sys
+
+import yaml
+
+EXTERNAL_FORMATS = {"date", "date-time", "uuid", "partial-date-time"}
+
+# v3 schemas the CLI *constructs* (request bodies) or that are dual-use
+# (request + response): keep them strict so required fields stay required at the
+# call site. Everything else in v3 components.schemas is a pure read and gets
+# relaxed (required dropped, arrays made nullable) to tolerate sparse payloads.
+STRICT_V3 = {
+ "AvailabilityCheckCriteria", # POST /check-availability body
+ "Registration", # POST /registrations body (and GET response)
+ "Consent",
+ "ConsentActor",
+ "Contact",
+ "Contacts",
+ "simple-address",
+ "InlineRegistrationProfile",
+ "DNSRecord", # POST /zones/{zone}/dns-records body
+ "NameServers", # PUT .../nameservers body
+ "NameserverHostname",
+}
+
+
+def strip_external_formats(obj):
+ """Drop string `format`s that progenitor maps to external crates (chrono/uuid)
+ and string `pattern`s that make it emit `regress::Regex` validation. The crate
+ keeps its dependency stack lean (no chrono/uuid/regress); these values pass
+ straight through as `String`, and the API validates them server-side (the CLI
+ surfaces any 422 field errors). Mirrors the upstream-v1 stripping in trim-spec.py."""
+ if isinstance(obj, dict):
+ if obj.get("type") == "string":
+ if obj.get("format") in EXTERNAL_FORMATS:
+ obj.pop("format")
+ # Drop string validation so progenitor emits plain `String` (or a thin
+ # alias for named schemas) instead of a validated newtype per field —
+ # the API validates these server-side and the CLI passes them through.
+ obj.pop("pattern", None)
+ obj.pop("minLength", None)
+ obj.pop("maxLength", None)
+ for v in obj.values():
+ strip_external_formats(v)
+ elif isinstance(obj, list):
+ for v in obj:
+ strip_external_formats(v)
+
+
+def only_2xx_paths(paths):
+ for item in paths.values():
+ if not isinstance(item, dict):
+ continue
+ for method, op in item.items():
+ if method not in ("get", "post", "put", "patch", "delete"):
+ continue
+ if isinstance(op, dict) and "responses" in op:
+ op["responses"] = {
+ code: r
+ for code, r in op["responses"].items()
+ if str(code).startswith("2")
+ }
+
+
+def relax_oas3(defn):
+ """Make an OAS3 response schema tolerant: drop `required` so each property is
+ optional, and mark array properties `nullable` so an explicit JSON `null`
+ deserializes to `None` rather than erroring."""
+ if not isinstance(defn, dict):
+ return
+ defn.pop("required", None)
+ for pschema in (defn.get("properties") or {}).values():
+ if isinstance(pschema, dict) and pschema.get("type") == "array":
+ pschema["nullable"] = True
+
+
+def schema_refs(obj):
+ """Collect the set of `#/components/schemas/` targets referenced in obj."""
+ out = set()
+ if isinstance(obj, dict):
+ ref = obj.get("$ref")
+ if isinstance(ref, str) and ref.startswith("#/components/schemas/"):
+ out.add(ref.split("/")[-1])
+ for v in obj.values():
+ out |= schema_refs(v)
+ elif isinstance(obj, list):
+ for v in obj:
+ out |= schema_refs(v)
+ return out
+
+
+def prune_unreferenced_schemas(doc):
+ """Drop component schemas unreachable from the (2xx-stripped) paths and the
+ other component sections. After stripping error responses this removes the
+ orphaned error-envelope model whose sanitized Rust name (`Error`) would
+ otherwise collide with the common `error` schema and make progenitor emit a
+ self-referential `struct Error(pub Error)`."""
+ schemas = doc.get("components", {}).get("schemas", {})
+ seed = schema_refs(doc.get("paths", {}))
+ for section, members in doc.get("components", {}).items():
+ if section != "schemas":
+ seed |= schema_refs(members)
+ reachable, stack = set(), list(seed)
+ while stack:
+ n = stack.pop()
+ if n in reachable or n not in schemas:
+ continue
+ reachable.add(n)
+ stack += list(schema_refs(schemas[n]))
+ for name in [k for k in schemas if k not in reachable]:
+ del schemas[name]
+
+
+def rewrite_refs(obj, rename):
+ """Rewrite every `#/components//` $ref via `rename(section, name)`."""
+ if isinstance(obj, dict):
+ ref = obj.get("$ref")
+ if isinstance(ref, str) and ref.startswith("#/components/"):
+ parts = ref.split("/")
+ if len(parts) == 4:
+ _, _, section, name = parts
+ obj["$ref"] = f"#/components/{section}/{rename(section, name)}"
+ for v in obj.values():
+ rewrite_refs(v, rename)
+ elif isinstance(obj, list):
+ for v in obj:
+ rewrite_refs(v, rename)
+
+
+def main(v1_path, v3_path, out_path, host):
+ v3 = yaml.safe_load(open(v3_path))
+ v1 = json.load(open(v1_path))
+
+ # --- v3: rewrite paths to absolute /v3/domains/..., pin servers to the host.
+ new_paths = {}
+ for key, item in v3.get("paths", {}).items():
+ if not key.startswith("/"):
+ continue # drop paths-level extensions (e.g. x-visibility)
+ new_paths["/v3/domains" + key] = item
+ v3["paths"] = new_paths
+ v3["servers"] = [{"url": host, "description": "Domains API host"}]
+
+ only_2xx_paths(v3["paths"])
+ strip_external_formats(v3)
+
+ # Relax pure-read v3 schemas; keep constructed/dual-use ones strict.
+ for name, schema in (v3.get("components", {}).get("schemas") or {}).items():
+ if name not in STRICT_V3:
+ relax_oas3(schema)
+
+ # `Registration` is dual-use: the register request requires `quoteToken`, but
+ # the register/get *responses* never echo it. progenitor emits one Rust type
+ # for both directions, so keeping `quoteToken` required makes response
+ # deserialization fail ("missing field quoteToken"). Mark just that field
+ # optional — the request handler always supplies it — while the other request
+ # fields (domain/consent) stay required.
+ registration = v3.get("components", {}).get("schemas", {}).get("Registration")
+ if isinstance(registration, dict) and "required" in registration:
+ registration["required"] = [
+ field for field in registration["required"] if field != "quoteToken"
+ ]
+
+ # --- v1: prefix every component name with V1, rewrite its $refs, then merge.
+ v1_components = v1.get("components", {})
+ rename = lambda section, name: f"V1{name}" # noqa: E731 — tiny local
+ # Rewrite refs in v1 paths and v1 component bodies first (they reference the
+ # OLD names; rewrite, then rename the keys).
+ rewrite_refs(v1.get("paths", {}), rename)
+ for section in v1_components.values():
+ if isinstance(section, dict):
+ rewrite_refs(section, rename)
+
+ v3.setdefault("components", {})
+ for section, members in v1_components.items():
+ if not isinstance(members, dict):
+ continue
+ dst = v3["components"].setdefault(section, {})
+ for name, body in members.items():
+ dst[f"V1{name}"] = body
+
+ only_2xx_paths(v1.get("paths", {}))
+ # v1 paths (/v1/...) never collide with the rewritten v3 paths (/v3/domains/...).
+ v3["paths"].update(v1.get("paths", {}))
+
+ # Drop schemas left unreferenced once error responses are stripped, so no two
+ # surviving schemas sanitize to the same Rust type name.
+ prune_unreferenced_schemas(v3)
+
+ json.dump(v3, open(out_path, "w"), indent=2)
+ n_paths = len(v3["paths"])
+ n_schemas = len(v3.get("components", {}).get("schemas", {}))
+ print(f" merged -> {out_path}: {n_paths} paths, {n_schemas} schemas")
+
+
+if __name__ == "__main__":
+ main(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
diff --git a/rust/domains-client/scripts/regenerate-spec.sh b/rust/domains-client/scripts/regenerate-spec.sh
index 8ef16f0..eb08804 100755
--- a/rust/domains-client/scripts/regenerate-spec.sh
+++ b/rust/domains-client/scripts/regenerate-spec.sh
@@ -1,37 +1,56 @@
#!/usr/bin/env bash
-# Regenerate the trimmed OpenAPI 3.0 spec the domains-client build consumes.
+# Regenerate the merged OpenAPI 3.0 spec the domains-client build consumes.
+#
+# The CLI's domain/dns commands span two API generations:
+# * v3 — the Domain Lifecycle Management API (availability, suggest, get,
+# registration quote→register, single DNS-record create, nameservers).
+# * v1 — the operations v3 does not yet serve (list, agreements, DNS record
+# list/set/delete).
#
# Pipeline:
# 1. Download the upstream GoDaddy Domains API spec (Swagger 2.0).
-# 2. Trim (via trim-spec.py) to the GET /v1/domains (list owned domains) and
-# GET /v1/domains/{domain} (get) operations, GET /v1/domains/available +
-# GET /v1/domains/suggest, GET /v1/domains/agreements, POST
-# /v1/domains/purchase + its GET /v1/domains/purchase/schema/{tld}, the v2
-# POST /v2/customers/{customerId}/domains/register operation, the
-# /v1/domains/{domain}/records DNS-record operations, and the transitive
-# closure of definitions they reference.
-# 3. Convert Swagger 2.0 -> OpenAPI 3.0 with `swagger2openapi` (Node, via npx).
+# 2. Trim (trim-spec.py) to the retained v1 operations + their definition closure.
+# 3. Convert that v1 subset Swagger 2.0 -> OpenAPI 3.0 with `swagger2openapi`.
+# 4. Merge (merge-spec.py) the v1 OAS3 into the vendored v3 spec, producing the
+# single domains.oas3.json progenitor consumes (build.rs never hits the network).
+#
+# The v3 spec (`openapi/swagger_domains.v3.yaml`) is a vendored, self-contained
+# bundle. Its source of truth is
+# `gdcorp-platform/domains.domain-lifecycle-specification` (v3/schemas), surfaced
+# as the pre-bundled `api-spec.yaml` in `gdcorp-domains/api-domain-v3-prototype`.
+# That repo is private, so refresh the vendored copy manually (authenticated):
+#
+# gh api repos/gdcorp-domains/api-domain-v3-prototype/contents/api-spec.yaml \
+# --jq '.content' | base64 -d > openapi/swagger_domains.v3.yaml
#
-# Run this ONLY when the upstream spec changes; the committed domains.oas3.json
-# is what the crate's build.rs feeds to progenitor (the build never hits the
-# network). Requires: curl, python3, and Node/npx (for swagger2openapi).
+# Run this script ONLY when either upstream spec changes. Requires: curl,
+# python3 (with PyYAML), and Node/npx (for swagger2openapi).
set -euo pipefail
here="$(cd "$(dirname "$0")/.." && pwd)" # domains-client/
openapi_dir="$here/openapi"
-v2="$openapi_dir/swagger_domains.v2.json"
-trimmed_v2="$openapi_dir/.swagger_domains.trimmed.v2.json"
+v2="$openapi_dir/swagger_domains.v2.json" # upstream Swagger 2.0 (v1+v2 source)
+v3="$openapi_dir/swagger_domains.v3.yaml" # vendored, self-contained v3 OAS3 bundle
+trimmed_v1="$openapi_dir/.swagger_domains.trimmed.v1.json"
+v1_oas3="$openapi_dir/.domains.v1.oas3.json"
oas3="$openapi_dir/domains.oas3.json"
+# The document host. base_url is overridden at runtime by the CLI per environment,
+# so this is cosmetic; keep it on the public OTE host.
+host="https://api.ote-godaddy.com"
+
echo "==> Downloading upstream Swagger 2.0 spec"
curl -fsSL "https://developer.godaddy.com/swagger/swagger_domains.json" -o "$v2"
-echo "==> Trimming to domains list + get + available + suggest + agreements + purchase (+ schema) + v2 register + DNS records and their definition closure"
-python3 "$here/scripts/trim-spec.py" "$v2" "$trimmed_v2"
+echo "==> Trimming to retained v1 ops (list + agreements + DNS record list/set/delete)"
+python3 "$here/scripts/trim-spec.py" "$v2" "$trimmed_v1"
+
+echo "==> Converting v1 subset Swagger 2.0 -> OpenAPI 3.0"
+# Pin the converter so regeneration is deterministic across time.
+npx -y swagger2openapi@7.0.8 "$trimmed_v1" -o "$v1_oas3"
+
+echo "==> Merging v1 OAS3 into the vendored v3 spec"
+python3 "$here/scripts/merge-spec.py" "$v1_oas3" "$v3" "$oas3" "$host"
-echo "==> Converting Swagger 2.0 -> OpenAPI 3.0"
-# Pin the converter so regeneration is deterministic across time (an unpinned
-# `npx swagger2openapi` would float to the latest release and can drift/break).
-npx -y swagger2openapi@7.0.8 "$trimmed_v2" -o "$oas3"
-rm -f "$trimmed_v2"
+rm -f "$trimmed_v1" "$v1_oas3"
echo "==> Wrote $oas3"
diff --git a/rust/domains-client/scripts/trim-spec.py b/rust/domains-client/scripts/trim-spec.py
index 40f92a3..9f8d6ee 100755
--- a/rust/domains-client/scripts/trim-spec.py
+++ b/rust/domains-client/scripts/trim-spec.py
@@ -1,17 +1,25 @@
#!/usr/bin/env python3
-"""Trim the upstream GoDaddy Domains Swagger 2.0 spec to the subset the
-domains-client build needs.
+"""Trim the upstream GoDaddy Domains Swagger 2.0 spec to the **retained v1**
+subset the domains-client build still needs after the v3 migration.
Invoked by `regenerate-spec.sh` as:
python3 trim-spec.py
-Keeps the domains list + get + availability + suggest + agreements + purchase
-(+ its per-TLD schema) + v2 register + DNS record operations and the transitive closure
-of definitions they reference,
-normalizes content types, and relaxes read-only response definitions (see the
-inline comments). The output is still Swagger 2.0; the shell script then runs
-`swagger2openapi` to produce the OpenAPI 3.0 spec progenitor consumes.
+v3 (the Domain Lifecycle Management API) now serves availability, suggestions,
+domain get, registration (quote → register), and single DNS-record creation.
+The operations v3 does NOT yet cover stay on v1 and are the only ones kept here:
+
+ * `GET /v1/domains` — list the shopper's domains
+ * `GET /v1/domains/agreements` — legal agreements for a TLD
+ * `GET /v1/domains/{domain}/records*` — list DNS records (all/by-type/by-name)
+ * `PUT /v1/domains/{domain}/records/{type}/{name}` — replace a record set (dns set)
+ * `DELETE /v1/domains/{domain}/records/{type}/{name}` — delete a record set (dns delete)
+
+Everything kept here is a *read* or a record mutation; the transitive closure of
+definitions they reference is kept too. The output is still Swagger 2.0; the
+shell script then converts it to OpenAPI 3.0 and `merge-spec.py` folds it into
+the v3 spec (prefixing these v1 definitions with `V1` to avoid name clashes).
"""
import copy
@@ -19,22 +27,17 @@
import sys
# GET-only operations kept as-is (operationId pinned). `list` retrieves the
-# Domains owned by the authenticated shopper; available/suggest are the
-# availability endpoints; agreements retrieves the legal agreements a TLD
-# requires before purchase (upstream operationId `getAgreement`, pinned here to
+# domains owned by the authenticated shopper; `agreements` retrieves the legal
+# agreements a TLD requires (upstream operationId `getAgreement`, pinned here to
# `agreements` so the generated builder reads `client.agreements()`).
AVAIL_OPS = {
"/v1/domains": "list",
- "/v1/domains/available": "available",
- "/v1/domains/suggest": "suggest",
"/v1/domains/agreements": "agreements",
}
-# DNS record operations: keep every method these paths expose. The upstream
-# Swagger documents `recordGet` only on the {type}/{name} path even though the
-# gateway also routes GET on the bare and {type} paths (see the OAuth scope
-# whitelist in gdcorp-domains/api-domain-data, api/oauthscopewhitelist.json);
-# we synthesize those two GETs below so the CLI can list all / by-type records.
+# DNS record operations. v3 only creates single records, so the read (list) and
+# the type+name replace/delete stay on v1. We keep every documented method on
+# these paths and synthesize the GET-all / GET-by-type reads below.
RECORD_PATHS = [
"/v1/domains/{domain}/records",
"/v1/domains/{domain}/records/{type}",
@@ -43,9 +46,9 @@
# String `format`s that typify maps to *external* crates (date-time/date ->
# chrono, uuid -> uuid). We don't depend on those (and deliberately keep this
-# crate's dependency stack lean — see Cargo.toml), and the CLI only passes these
-# values straight through to JSON output, so drop the format and let them
-# generate as plain `String` (the RFC 3339 / UUID text is unchanged on the wire).
+# crate's dependency stack lean), and the CLI only passes these values straight
+# through to JSON output, so drop the format and let them generate as plain
+# `String` (the RFC 3339 / UUID text is unchanged on the wire).
EXTERNAL_FORMATS = {"date", "date-time", "uuid", "partial-date-time"}
# Read-only response definitions the CLI only deserializes-and-reprints. The
@@ -54,26 +57,12 @@
# cancelled/pending domains) and types `nameServers` as a non-null array while
# the API returns JSON `null`. Relax these to tolerant readers.
#
-# The complement — types the CLI *constructs* (request bodies) or reads via
-# non-optional fields (e.g. `if available { … }`) — must stay strict, or call
-# sites break. Everything not listed here is relaxed.
+# The complement — types the CLI *constructs* (record request bodies) — must stay
+# strict, or call sites break. Everything not listed here is relaxed.
# NB: these are the *spec* definition names (upper-case `DNS…`), not the
# camel-cased Rust type names progenitor emits (`DnsRecord`).
STRICT_DEFS = {
- "DomainAvailableResponse", # `available`/`suggest` read non-optional fields
- "DomainSuggestion",
- "DNSRecord", # built by `dns add`
- "DNSRecordCreateType",
"DNSRecordCreateTypeName", # built by `dns set`
- "ArrayOfDNSRecord", # `dns add` request body
- "DomainPurchase", # `domain purchase` request body
- "Consent", # required sub-object of DomainPurchase
- "Contact", # optional contacts; keep required fields strict
- "Address", # contact mailing address (constructed for v2 contacts)
- "DomainPurchaseV2", # v2 `domain purchase` request body (OAuth path)
- "ConsentV2", # required sub-object of DomainPurchaseV2
- "DomainContactsCreateV2", # v2 contacts wrapper
- "ContactDomainCreate", # v2 per-role contact (reuses Address)
}
@@ -152,63 +141,13 @@ def main(src, dst):
paths = {}
- # Availability + suggest + agreements (GET only).
+ # Reads kept verbatim (GET only): list + agreements.
for p, op_id in AVAIL_OPS.items():
get = d["paths"][p]["get"]
get["operationId"] = op_id
get["produces"] = ["application/json"] # drop xml/js variants
paths[p] = {"get": only_2xx(get)}
- # Purchase (POST with JSON body). The body ($ref DomainPurchase) and its
- # transitive closure (Consent/Contact/Address) are kept strict below so the
- # generated request types keep their required fields.
- purchase = d["paths"]["/v1/domains/purchase"]["post"]
- purchase["operationId"] = "purchase"
- purchase["produces"] = ["application/json"]
- purchase["consumes"] = ["application/json"]
- paths["/v1/domains/purchase"] = {"post": only_2xx(purchase)}
-
- # Per-TLD purchase schema (GET). `domain purchase` reads its top-level
- # `required` array to preflight-validate requirements before the paid call.
- # The upstream JsonSchema/JsonProperty/JsonDataType definitions are loosely
- # typed (`"type": "object"` *with* `items`), so instead of pulling them into
- # the closure we replace the response with a free-form object — progenitor
- # then emits `serde_json::Value`, and we parse only the fields we need.
- schema_op = d["paths"]["/v1/domains/purchase/schema/{tld}"]["get"]
- schema_op["operationId"] = "schema"
- schema_op["produces"] = ["application/json"]
- only_2xx(schema_op)
- schema_op["responses"]["200"]["schema"] = {"type": "object"}
- paths["/v1/domains/purchase/schema/{tld}"] = {"get": schema_op}
-
- # Get one domain's details (GET). `DomainDetail` embeds the Contact/Address
- # types we keep strict for v2 request construction, but as a *read* the API
- # can return them sparsely (e.g. privacy-protected domains). Rather than fight
- # that strict/relax conflict, take the response as a free-form object —
- # progenitor emits `serde_json::Map` — and emit it directly (the command just
- # dumps the domain's details).
- get_op = d["paths"]["/v1/domains/{domain}"]["get"]
- get_op["operationId"] = "get"
- get_op["produces"] = ["application/json"]
- only_2xx(get_op)
- # `get` has two success responses (200 and 203), both DomainDetail; free-form
- # every kept 2xx so progenitor sees a single response type (and no DomainDetail
- # closure is pulled in).
- for resp in get_op["responses"].values():
- resp["schema"] = {"type": "object"}
- paths["/v1/domains/{domain}"] = {"get": get_op}
-
- # v2 register (POST). The OAuth-friendly purchase path: unlike v1 purchase,
- # v2 authorizes credit-card payments for OAuth bearer users. Body
- # ($ref DomainPurchaseV2) + closure (ConsentV2/DomainContactsCreateV2/
- # ContactDomainCreate, reusing Address) are kept strict below. Upstream has
- # no operationId, so pin `register`. The 2xx is a bodyless 202 (async).
- register = d["paths"]["/v2/customers/{customerId}/domains/register"]["post"]
- register["operationId"] = "register"
- register["produces"] = ["application/json"]
- register["consumes"] = ["application/json"]
- paths["/v2/customers/{customerId}/domains/register"] = {"post": only_2xx(register)}
-
# DNS record ops: keep all documented methods, success responses only, and
# normalize content types (drop xml/js variants; request bodies stay JSON).
for p in RECORD_PATHS:
@@ -259,7 +198,7 @@ def synth_get(keep_param_names, op_id):
out = {
"swagger": "2.0",
"info": {
- "title": "GoDaddy Domains API (domains list + get + availability + agreements + purchase + v2 register + DNS records subset)",
+ "title": "GoDaddy Domains API (retained v1 subset: list + agreements + DNS record list/set/delete)",
"version": d.get("info", {}).get("version", "1.0.0"),
},
"host": d.get("host", "api.ote-godaddy.com"),
diff --git a/rust/domains-client/src/lib.rs b/rust/domains-client/src/lib.rs
index bec1016..7bac1d7 100644
--- a/rust/domains-client/src/lib.rs
+++ b/rust/domains-client/src/lib.rs
@@ -1,11 +1,20 @@
-//! GoDaddy Domains API client (domains list + get + availability + suggest +
-//! agreements + purchase + per-TLD purchase schema + v2 register + DNS records).
+//! GoDaddy Domains API client, spanning two API generations behind one host:
+//!
+//! * **v3** — the Domain Lifecycle Management API (`/v3/domains/…`): suggestions,
+//! availability (single + batch), domain get, registration (quote → register),
+//! async operation polling, single DNS-record create, and nameserver replace.
+//! * **v1** — the operations v3 does not yet serve (`/v1/domains/…`): list the
+//! shopper's domains, TLD legal agreements, and DNS record list/replace/delete.
+//! Their generated types are `V1`-prefixed to avoid clashing with the v3 ones.
//!
//! The contents of this crate are **generated** by `progenitor` at build time
-//! from the vendored OpenAPI 3.0 spec (`openapi/domains.oas3.json`). Construct
-//! [`Client`] with [`Client::new_with_client`] to supply a pre-authenticated
-//! `reqwest::Client` (the CLI sets the `Authorization: sso-key …`/Bearer header
-//! itself). See `scripts/regenerate-spec.sh` to refresh the spec.
+//! from the vendored, merged OpenAPI 3.0 spec (`openapi/domains.oas3.json`).
+//! Construct [`Client`] with [`Client::new_with_client`] to supply a
+//! pre-authenticated `reqwest::Client` (the CLI sets the `Authorization:
+//! Bearer ` header itself). The v3 operations live under the
+//! `/v3/domains` base path, baked into the spec's absolute paths so one host
+//! `base_url` serves both generations. See `scripts/regenerate-spec.sh` to
+//! refresh and re-merge the spec.
//!
//! The lint allowances are scoped to the generated module so the hand-written
//! code below (`client_with_auth`, `BuildError`) is still linted normally.
@@ -36,9 +45,9 @@ pub enum BuildError {
/// header and `x-request-id`.
///
/// `authorization` is the full header value the domain endpoints expect — e.g.
-/// `"sso-key :"` (the usual path) or `"Bearer "`. Keeping
-/// the `reqwest::Client` construction here means callers never name reqwest's
-/// types, so the main crate is unaffected by this crate's reqwest version.
+/// `"Bearer "`. Keeping the `reqwest::Client` construction here means
+/// callers never name reqwest's types, so the main crate is unaffected by this
+/// crate's reqwest version.
pub fn client_with_auth(
base_url: &str,
authorization: &str,
@@ -63,409 +72,417 @@ pub fn client_with_auth(
#[cfg(test)]
mod tests {
use super::*;
- use httpmock::Method::PATCH; // not re-exported by the prelude (unlike GET/PUT/DELETE)
use httpmock::prelude::*;
use serde_json::json;
// These tests exercise the generated request/response wiring against a mock
- // server: the query-parameter names (which guard the builder setters →
- // wire-parameter mapping at the call sites), the `Authorization`/
- // `x-request-id`/`api-version` headers set by `client_with_auth`, and response
- // deserialization. They run entirely offline.
+ // server: HTTP method + path (v3 lives under /v3/domains/…, v1 under
+ // /v1/domains/…), the query-parameter / body field names that map the builder
+ // setters to the wire, the `Authorization` / `x-request-id` / `Idempotency-Key`
+ // headers, and response deserialization. They run entirely offline.
+
+ fn client_for(server: &MockServer) -> Client {
+ client_with_auth(
+ &server.base_url(),
+ "Bearer tok",
+ "godaddy-cli/test",
+ "req-1",
+ )
+ .expect("build client")
+ }
+
+ // --- v3: discovery ------------------------------------------------------
+
+ #[tokio::test]
+ async fn suggest_domains_maps_setters_to_named_query_params() {
+ let server = MockServer::start_async().await;
+ let mock = server
+ .mock_async(|when, then| {
+ when.method(GET)
+ .path("/v3/domains/suggestions")
+ .query_param("query", "coffee")
+ .query_param("pageSize", "5")
+ .query_param("tlds", "com")
+ .header("authorization", "Bearer tok");
+ then.status(200)
+ .json_body(json!({ "items": [{ "domain": "coffeehouse.com" }] }));
+ })
+ .await;
+
+ let resp = client_for(&server)
+ .suggest_domains()
+ .query("coffee")
+ .page_size(5)
+ .tlds(vec!["com".to_string()])
+ .send()
+ .await
+ .expect("request succeeds")
+ .into_inner();
+
+ mock.assert_async().await;
+ assert_eq!(resp.items.len(), 1);
+ assert_eq!(resp.items[0].domain.as_deref(), Some("coffeehouse.com"));
+ }
#[tokio::test]
- async fn available_sends_correct_request_and_parses_response() {
+ async fn get_domain_availability_single_parses_prices() {
let server = MockServer::start_async().await;
let mock = server
.mock_async(|when, then| {
when.method(GET)
- .path("/v1/domains/available")
- .query_param("domain", "example.com")
- .query_param("checkType", "FULL")
- .query_param("forTransfer", "true")
- .header("authorization", "sso-key KEY:SECRET")
- .header("x-request-id", "req-123")
- .header("api-version", "1.0.0");
+ .path("/v3/domains/check-availability")
+ .query_param("domain", "example.com");
then.status(200).json_body(json!({
"domain": "example.com",
- "available": false,
+ "available": true,
"definitive": true,
- "price": 11_990_000,
- "currency": "USD",
- "renewalPrice": 21_990_000,
- "period": 1
+ // v3 money is ISO-4217 minor units: USD 11.99 -> 1199 (not v1 micro-units).
+ "prices": [{ "period": 1, "price": { "currencyCode": "USD", "value": 1199 } }]
}));
})
.await;
- let client = client_with_auth(
- &server.base_url(),
- "sso-key KEY:SECRET",
- "godaddy-cli/test",
- "req-123",
- )
- .expect("build client");
-
- let body = client
- .available()
+ let body = client_for(&server)
+ .get_domain_availability()
.domain("example.com")
- .check_type(types::AvailableCheckType::Full)
- .for_transfer(true)
.send()
.await
.expect("request succeeds")
.into_inner();
mock.assert_async().await;
- assert_eq!(body.domain, "example.com");
- assert!(!body.available);
- assert!(body.definitive);
- assert_eq!(body.price, Some(11_990_000));
- assert_eq!(body.currency, "USD");
- assert_eq!(body.renewal_price, Some(21_990_000));
- assert_eq!(body.period, Some(1));
+ assert_eq!(body.domain.as_deref(), Some("example.com"));
+ assert_eq!(body.available, Some(true));
+ let prices = body.prices.expect("prices present");
+ assert_eq!(prices[0].price.as_ref().and_then(|m| m.value), Some(1199));
}
#[tokio::test]
- async fn available_with_bearer_scheme_sets_header() {
+ async fn check_availability_batch_posts_domains_array() {
let server = MockServer::start_async().await;
let mock = server
.mock_async(|when, then| {
- when.method(GET)
- .path("/v1/domains/available")
- .query_param("domain", "open.dev")
- .header("authorization", "Bearer tok-abc");
+ when.method(POST)
+ .path("/v3/domains/check-availability")
+ .json_body(json!({ "domains": ["a.com", "b.com"], "optimizeFor": "SPEED" }));
then.status(200).json_body(json!({
- "domain": "open.dev",
- "available": true,
- "definitive": true
+ "items": [
+ { "domain": "a.com", "available": true },
+ { "domain": "b.com", "available": false }
+ ]
}));
})
.await;
- let client = client_with_auth(
- &server.base_url(),
- "Bearer tok-abc",
- "godaddy-cli/test",
- "req-1",
- )
- .expect("build client");
-
- let body = client
- .available()
- .domain("open.dev")
+ let body = client_for(&server)
+ .check_availability()
+ .body(types::AvailabilityCheckCriteria {
+ domains: vec!["a.com".to_string(), "b.com".to_string()],
+ optimize_for: types::OptimizationTarget::Speed,
+ isc_code: None,
+ })
.send()
.await
.expect("request succeeds")
.into_inner();
mock.assert_async().await;
- assert!(body.available);
- // Optional fields absent in the response deserialize to None.
- assert_eq!(body.price, None);
- assert_eq!(body.currency, "USD"); // serde default
+ assert_eq!(body.items.len(), 2);
+ assert_eq!(body.items[1].available, Some(false));
}
+ // --- v3: registration (quote → register → poll) -------------------------
+
#[tokio::test]
- async fn suggest_maps_positional_args_to_named_query_params() {
+ async fn quote_registration_posts_body_and_parses_token_and_agreements() {
let server = MockServer::start_async().await;
- // Asserting each value lands in the correctly *named* query param guards
- // the builder setter -> wire-parameter mapping (e.g. that `.city(..)`
- // really sends `city=`, not some other param) across spec regenerations.
let mock = server
.mock_async(|when, then| {
- when.method(GET)
- .path("/v1/domains/suggest")
- .query_param("query", "coffee")
- .query_param("city", "Phoenix")
- .query_param("country", "US")
- .query_param("limit", "5")
- .query_param("tlds", "com");
- then.status(200).json_body(json!([
- { "domain": "coffeehouse.com" },
- { "domain": "bestcoffee.com" }
- ]));
+ when.method(POST)
+ .path("/v3/domains/registration-quotes")
+ .json_body(json!({ "domain": "example.com", "period": 2 }));
+ then.status(200).json_body(json!({
+ "domain": "example.com",
+ "available": true,
+ "quoteToken": "tok-abc",
+ "period": 2,
+ // v3 money is ISO-4217 minor units: USD 23.98 (2yr) -> 2398.
+ "price": { "currencyCode": "USD", "value": 2398 },
+ "requiredAgreements": [
+ { "agreementType": "REGISTRATION", "title": "Registration Agreement",
+ "url": "https://x/agr" }
+ ]
+ }));
})
.await;
- let client = client_with_auth(
- &server.base_url(),
- "Bearer tok",
- "godaddy-cli/test",
- "req-2",
- )
- .expect("build client");
-
- let suggestions = client
- .suggest()
- .query("coffee")
- .city("Phoenix")
- .country(types::SuggestCountry::Us)
- .limit(5)
- .tlds(vec!["com".to_string()])
+ let quote = client_for(&server)
+ .quote_domain_registration()
+ .body(types::QuoteDomainRegistrationBody {
+ domain: "example.com".to_string(),
+ period: std::num::NonZeroU64::new(2).expect("nonzero"),
+ profile: None,
+ profile_id: None,
+ })
.send()
.await
.expect("request succeeds")
.into_inner();
mock.assert_async().await;
- let domains: Vec<&str> = suggestions.iter().map(|s| s.domain.as_str()).collect();
- assert_eq!(domains, ["coffeehouse.com", "bestcoffee.com"]);
+ assert_eq!(quote.available, Some(true));
+ assert_eq!(
+ quote.quote_token.as_ref().map(|t| t.as_str()),
+ Some("tok-abc")
+ );
+ let agreements = quote.required_agreements.expect("agreements");
+ assert_eq!(
+ agreements[0].agreement_type.as_ref().map(|a| a.as_str()),
+ Some("REGISTRATION")
+ );
}
#[tokio::test]
- async fn list_tolerates_sparse_payloads() {
- // The published spec marks fields like `contactRegistrant`/`renewDeadline`
- // required and types `nameServers` as a non-null array, but the live API
- // omits the former and returns `nameServers: null` for many domains
- // (cancelled/pending). The generated `DomainSummary` must read these
- // without erroring. Payload mirrors a real `GET /v1/domains` response.
+ async fn register_sends_idempotency_key_and_consent_then_accepts_202() {
let server = MockServer::start_async().await;
let mock = server
.mock_async(|when, then| {
- when.method(GET).path("/v1/domains");
- then.status(200).json_body(json!([
- {
- "createdAt": "2021-09-24T15:08:06.000Z",
- "deletedAt": "2024-11-05T02:30:31.000Z",
- "domain": "blahblahblah253.com",
- "domainId": 21605119,
- "expirationProtected": true,
- "expires": "2024-09-24T15:08:06.000Z",
- "exposeWhois": false,
- "holdRegistrar": false,
- "locked": true,
- "nameServers": null,
- "privacy": true,
- "renewAuto": false,
- "renewable": false,
- "status": "CANCELLED",
- "transferProtected": true
+ when.method(POST)
+ .path("/v3/domains/registrations")
+ .header("Idempotency-Key", "idem-123")
+ .json_body(json!({
+ "domain": "example.com",
+ "period": 1,
+ "quoteToken": "tok-abc",
+ "consent": {
+ "agreedAt": "2026-06-30T00:00:00Z",
+ "agreedBy": { "type": "DIRECT", "principal": "shopper-42", "ip": "127.0.0.1" },
+ "agreementTypes": ["REGISTRATION"]
+ }
+ }));
+ // The register response does NOT echo `quoteToken` (the token is
+ // single-use and consumed); the client's `Registration` type must
+ // parse it anyway — quote_token is optional for exactly this reason.
+ then.status(202).json_body(json!({
+ "domain": "example.com",
+ "period": 1,
+ "consent": {
+ "agreedAt": "2026-06-30T00:00:00Z",
+ "agreedBy": { "type": "DIRECT", "principal": "shopper-42" },
+ "agreementTypes": ["REGISTRATION"]
},
- {
- "createdAt": "2020-10-27T13:40:15.463Z",
- "domain": "dullreferenceexception.me",
- "domainId": 21507912,
- "expirationProtected": false,
- "exposeWhois": false,
- "holdRegistrar": false,
- "locked": false,
- "nameServers": null,
- "privacy": false,
- "renewAuto": false,
- "renewable": false,
- "status": "PENDING_DNS_ACTIVE",
- "transferProtected": false
- }
- ]));
+ "registrationId": "reg-1",
+ "operationId": "op-1",
+ "status": "PENDING"
+ }));
})
.await;
- let body = client_for(&server)
- .list()
+ let reg = client_for(&server)
+ .register_domain()
+ .idempotency_key("idem-123")
+ .body(types::Registration {
+ consent: types::Consent {
+ agreed_at: types::DateTime("2026-06-30T00:00:00Z".to_string()),
+ agreed_by: types::ConsentActor {
+ actor: None,
+ ip: Some("127.0.0.1".to_string()),
+ principal: "shopper-42".to_string(),
+ type_: types::ConsentActorType("DIRECT".to_string()),
+ },
+ agreement_types: vec![types::AgreementType("REGISTRATION".to_string())],
+ },
+ created_at: None,
+ domain: "example.com".to_string(),
+ expires_at: None,
+ links: vec![],
+ operation_id: None,
+ period: std::num::NonZeroU64::new(1).expect("nonzero"),
+ profile: None,
+ profile_id: None,
+ quote_token: Some(types::Uuid("tok-abc".to_string())),
+ registration_id: None,
+ status: None,
+ updated_at: None,
+ })
.send()
.await
- .expect("sparse list payload parses")
+ .expect("202 accepted")
.into_inner();
mock.assert_async().await;
- assert_eq!(body.len(), 2);
- }
-
- // --- DNS records ---------------------------------------------------------
- //
- // These guard the spec-generated record operations: the HTTP method + path
- // (including the `{domain}`/`{type}`/`{name}` path segments and the
- // synthesized list-all GET), the JSON request bodies the builder serializes,
- // and response parsing. They run entirely offline against a mock server.
-
- fn client_for(server: &MockServer) -> Client {
- client_with_auth(
- &server.base_url(),
- "Bearer tok",
- "godaddy-cli/test",
- "req-rec",
- )
- .expect("build client")
+ assert_eq!(reg.operation_id.as_ref().map(|o| o.as_str()), Some("op-1"));
}
#[tokio::test]
- async fn record_get_all_lists_every_record() {
- // No type/name -> the synthesized GET on the bare `/records` path.
+ async fn get_operation_polls_status() {
let server = MockServer::start_async().await;
let mock = server
.mock_async(|when, then| {
- when.method(GET)
- .path("/v1/domains/example.com/records")
- .header("authorization", "Bearer tok");
- then.status(200).json_body(json!([
- { "type": "A", "name": "www", "data": "1.2.3.4", "ttl": 600 },
- { "type": "TXT", "name": "@", "data": "v=spf1 -all" }
- ]));
+ when.method(GET).path("/v3/domains/operations/op-1");
+ then.status(200).json_body(json!({
+ "operationId": "op-1",
+ "type": "REGISTER",
+ "domain": "example.com",
+ "status": "COMPLETED"
+ }));
})
.await;
- let records = client_for(&server)
- .record_get_all()
- .domain("example.com")
+ let op = client_for(&server)
+ .get_operation()
+ .operation_id(types::Uuid("op-1".to_string()))
.send()
.await
.expect("request succeeds")
.into_inner();
mock.assert_async().await;
- assert_eq!(records.len(), 2);
- assert_eq!(records[0].type_, types::DnsRecordType::A);
- assert_eq!(records[0].name, "www");
- assert_eq!(records[0].data, "1.2.3.4");
- assert_eq!(records[0].ttl, Some(600));
- assert_eq!(records[1].type_, types::DnsRecordType::Txt);
+ assert_eq!(op.status.as_ref().map(|s| s.as_str()), Some("COMPLETED"));
}
+ // --- v3: domain get + nameservers + dns create --------------------------
+
#[tokio::test]
- async fn record_get_sends_type_name_and_pagination() {
+ async fn get_domain_reads_v3_path() {
let server = MockServer::start_async().await;
let mock = server
.mock_async(|when, then| {
when.method(GET)
- .path("/v1/domains/example.com/records/A/www")
- .query_param("limit", "10")
- .query_param("offset", "5");
- then.status(200)
- .json_body(json!([{ "type": "A", "name": "www", "data": "1.2.3.4" }]));
+ .path("/v3/domains/domain-names/example.com");
+ then.status(200).json_body(json!({
+ "domain": "example.com",
+ "status": "ACTIVE",
+ "autoRenew": true
+ }));
})
.await;
- let records = client_for(&server)
- .record_get()
- .domain("example.com")
- .type_("A")
- .name("www")
- .limit(10)
- .offset(5)
+ let detail = client_for(&server)
+ .get_domain()
+ .domain_name("example.com")
.send()
.await
.expect("request succeeds")
.into_inner();
mock.assert_async().await;
- assert_eq!(records.len(), 1);
- assert_eq!(records[0].data, "1.2.3.4");
+ assert_eq!(detail.domain.as_deref(), Some("example.com"));
+ assert_eq!(detail.auto_renew, Some(true));
}
#[tokio::test]
- async fn record_add_patches_a_record_array() {
+ async fn create_dns_record_posts_single_record_to_zone() {
let server = MockServer::start_async().await;
let mock = server
.mock_async(|when, then| {
- when.method(PATCH)
- .path("/v1/domains/example.com/records")
- .json_body(json!([{ "data": "1.2.3.4", "name": "www", "type": "A" }]));
- then.status(200);
+ when.method(POST)
+ .path("/v3/domains/zones/example.com/dns-records")
+ .json_body(
+ json!({ "type": "A", "name": "www", "data": "1.2.3.4", "ttl": 600 }),
+ );
+ then.status(201).json_body(
+ json!({ "type": "A", "name": "www", "data": "1.2.3.4", "ttl": 600 }),
+ );
})
.await;
- client_for(&server)
- .record_add()
- .domain("example.com")
- .body(vec![types::DnsRecord {
+ let rec = client_for(&server)
+ .create_dns_record()
+ .zone("example.com")
+ .body(types::DnsRecord {
data: "1.2.3.4".to_string(),
+ flag: None,
name: "www".to_string(),
- type_: types::DnsRecordType::A,
- ttl: None,
- priority: None,
port: None,
- weight: None,
+ priority: None,
protocol: None,
+ record_id: None,
service: None,
- }])
+ tag: None,
+ ttl: 600,
+ type_: types::DnsRecordType("A".to_string()),
+ weight: None,
+ })
.send()
.await
- .expect("request succeeds");
+ .expect("request succeeds")
+ .into_inner();
mock.assert_async().await;
+ assert_eq!(rec.name, "www");
+ assert_eq!(rec.ttl, 600);
}
#[tokio::test]
- async fn record_replace_type_name_puts_the_record_set() {
+ async fn update_nameservers_puts_hostname_array() {
let server = MockServer::start_async().await;
let mock = server
.mock_async(|when, then| {
when.method(PUT)
- .path("/v1/domains/example.com/records/A/www")
- .json_body(json!([{ "data": "5.6.7.8", "ttl": 600 }]));
- then.status(200);
+ .path("/v3/domains/domain-names/example.com/nameservers")
+ .header("Idempotency-Key", "idem-9")
+ .json_body(json!(["ns1.example.net", "ns2.example.net"]));
+ then.status(202)
+ .json_body(json!({ "operationId": "op-2", "type": "UPDATE_NAMESERVERS" }));
})
.await;
- client_for(&server)
- .record_replace_type_name()
- .domain("example.com")
- .type_("A")
- .name("www")
- .body(vec![types::DnsRecordCreateTypeName {
- data: "5.6.7.8".to_string(),
- ttl: Some(600),
- priority: None,
- port: None,
- weight: None,
- protocol: None,
- service: None,
- }])
+ let op = client_for(&server)
+ .update_nameservers()
+ .domain_name("example.com")
+ .idempotency_key("idem-9")
+ .body(types::NameServers(vec![
+ types::NameserverHostname("ns1.example.net".to_string()),
+ types::NameserverHostname("ns2.example.net".to_string()),
+ ]))
.send()
.await
- .expect("request succeeds");
+ .expect("202 accepted")
+ .into_inner();
mock.assert_async().await;
+ assert_eq!(op.operation_id.as_ref().map(|o| o.as_str()), Some("op-2"));
}
+ // --- retained v1: list + agreements + DNS list/set/delete ---------------
+
#[tokio::test]
- async fn record_delete_type_name_issues_delete_and_accepts_204() {
+ async fn v1_list_tolerates_sparse_payloads() {
let server = MockServer::start_async().await;
let mock = server
.mock_async(|when, then| {
- when.method(DELETE)
- .path("/v1/domains/example.com/records/A/www");
- then.status(204);
+ when.method(GET).path("/v1/domains");
+ then.status(200).json_body(json!([
+ { "domain": "a.com", "status": "ACTIVE", "nameServers": null },
+ { "domain": "b.me", "status": "PENDING_DNS_ACTIVE", "nameServers": null }
+ ]));
})
.await;
- client_for(&server)
- .record_delete_type_name()
- .domain("example.com")
- .type_("A")
- .name("www")
+ let body = client_for(&server)
+ .list()
.send()
.await
- .expect("request succeeds");
+ .expect("sparse list parses")
+ .into_inner();
mock.assert_async().await;
+ assert_eq!(body.len(), 2);
+ assert_eq!(body[0].domain.as_deref(), Some("a.com"));
}
- // --- agreements + purchase ----------------------------------------------
- //
- // The legal-agreements GET (the consent prerequisite) and the purchase POST.
- // These guard the agreements query-param names, the JSON request body the
- // purchase builder serializes (domain + consent + the always-present period/
- // privacy/renewAuto, contacts omitted), and response parsing. Offline.
-
#[tokio::test]
- async fn agreements_sends_query_params_and_parses_list() {
+ async fn v1_agreements_sends_query_params_and_parses_list() {
let server = MockServer::start_async().await;
let mock = server
.mock_async(|when, then| {
when.method(GET)
.path("/v1/domains/agreements")
.query_param("tlds", "com")
- .query_param("privacy", "false")
- .query_param("forTransfer", "false");
+ .query_param("privacy", "false");
then.status(200).json_body(json!([
- {
- "agreementKey": "DNRA",
- "title": "Domain Name Registration Agreement",
- "url": "https://www.godaddy.com/agreements/showdoc?id=reg_sa",
- "content": "full text"
- }
+ { "agreementKey": "DNRA", "title": "Registration Agreement", "url": "https://x" }
]));
})
.await;
@@ -474,256 +491,93 @@ mod tests {
.agreements()
.tlds(vec!["com".to_string()])
.privacy(false)
- .for_transfer(false)
.send()
.await
.expect("request succeeds")
.into_inner();
mock.assert_async().await;
- assert_eq!(agreements.len(), 1);
assert_eq!(agreements[0].agreement_key.as_deref(), Some("DNRA"));
- assert_eq!(
- agreements[0].title.as_deref(),
- Some("Domain Name Registration Agreement")
- );
}
#[tokio::test]
- async fn purchase_serializes_body_and_parses_order() {
+ async fn v1_record_get_all_lists_records() {
let server = MockServer::start_async().await;
- // Contacts are omitted (account defaults) so they don't appear in the
- // body; period/privacy/renewAuto always serialize (serde defaults, no
- // skip), which guards their wire names (`period`/`privacy`/`renewAuto`).
let mock = server
.mock_async(|when, then| {
- when.method(POST)
- .path("/v1/domains/purchase")
- .json_body(json!({
- "domain": "example.com",
- "consent": {
- "agreedAt": "2026-06-17T00:00:00Z",
- "agreedBy": "203.0.113.7",
- "agreementKeys": ["DNRA"]
- },
- "period": 1,
- "privacy": false,
- "renewAuto": true
- }));
- then.status(200).json_body(json!({
- "orderId": 1_234_567,
- "itemCount": 1,
- "total": 11_990_000,
- "currency": "USD"
- }));
+ when.method(GET).path("/v1/domains/example.com/records");
+ then.status(200).json_body(
+ json!([{ "type": "A", "name": "www", "data": "1.2.3.4", "ttl": 600 }]),
+ );
})
.await;
- let body = client_for(&server)
- .purchase()
- .body(types::DomainPurchase {
- domain: "example.com".to_string(),
- consent: types::Consent {
- agreed_at: "2026-06-17T00:00:00Z".to_string(),
- agreed_by: "203.0.113.7".to_string(),
- agreement_keys: vec!["DNRA".to_string()],
- },
- contact_registrant: None,
- contact_admin: None,
- contact_billing: None,
- contact_tech: None,
- name_servers: vec![],
- period: std::num::NonZeroU64::new(1).expect("nonzero period"),
- privacy: false,
- renew_auto: true,
- })
- .send()
- .await
- .expect("request succeeds")
- .into_inner();
-
- mock.assert_async().await;
- assert_eq!(body.order_id, Some(1_234_567));
- assert_eq!(body.item_count, Some(1));
- assert_eq!(body.total, Some(11_990_000));
- assert_eq!(body.currency, "USD");
- }
-
- #[tokio::test]
- async fn schema_fetches_per_tld_requirements_as_free_form_json() {
- // The per-TLD purchase schema is returned untyped (a serde_json map), so
- // `domain purchase` can read just the top-level `required` array. This
- // guards the `{tld}` path param and the free-form response decode.
- let server = MockServer::start_async().await;
- let mock = server
- .mock_async(|when, then| {
- when.method(GET).path("/v1/domains/purchase/schema/fun");
- then.status(200).json_body(json!({
- "id": "fun",
- "required": ["domain", "consent", "contactRegistrant"],
- "properties": {},
- "models": {}
- }));
- })
- .await;
-
- let schema = client_for(&server)
- .schema()
- .tld("fun")
+ let records = client_for(&server)
+ .record_get_all()
+ .domain("example.com")
.send()
.await
.expect("request succeeds")
.into_inner();
mock.assert_async().await;
- let required: Vec<&str> = schema
- .get("required")
- .and_then(|v| v.as_array())
- .expect("required array")
- .iter()
- .filter_map(|v| v.as_str())
- .collect();
- assert!(required.contains(&"contactRegistrant"));
+ assert_eq!(records.len(), 1);
+ assert_eq!(records[0].data.as_deref(), Some("1.2.3.4"));
}
#[tokio::test]
- async fn register_v2_posts_to_customer_path_and_accepts_202() {
- // The OAuth purchase path: POST to the customer-scoped v2 register with a
- // DomainPurchaseV2 body, returning a bodyless 202. This guards the
- // `{customerId}` path segment, the serialized request shape the domains
- // API receives (consent with price/currency, the v2 contact with its
- // ASCII encoding), and the no-body 2xx decode.
+ async fn v1_record_replace_type_name_puts_record_set() {
let server = MockServer::start_async().await;
let mock = server
.mock_async(|when, then| {
- when.method(POST)
- .path("/v2/customers/cust-123/domains/register")
- .json_body(json!({
- "domain": "example.fun",
- "consent": {
- "agreedAt": "2026-06-18T00:00:00Z",
- "agreedBy": "127.0.0.1",
- "agreementKeys": ["DNRA"],
- "currency": "USD",
- "price": 11_990_000
- },
- "period": 1,
- "privacy": false,
- "renewAuto": true,
- "contacts": {
- "registrant": {
- "addressMailing": {
- "address1": "1 A St",
- "city": "Tempe",
- "country": "US",
- "postalCode": "85281",
- "state": "AZ"
- },
- "email": "a@example.com",
- "encoding": "ASCII",
- "nameFirst": "Ada",
- "nameLast": "Lovelace",
- "phone": "+1.4805551212"
- }
- }
- }));
- then.status(202);
+ when.method(PUT)
+ .path("/v1/domains/example.com/records/A/www")
+ .json_body(json!([{ "data": "5.6.7.8", "ttl": 600 }]));
+ then.status(200);
})
.await;
client_for(&server)
- .register()
- .customer_id("cust-123")
- .body(types::DomainPurchaseV2 {
- domain: "example.fun".to_string(),
- consent: types::ConsentV2 {
- agreed_at: "2026-06-18T00:00:00Z".to_string(),
- agreed_by: "127.0.0.1".to_string(),
- agreement_keys: vec!["DNRA".to_string()],
- claim_token: None,
- currency: "USD".to_string(),
- price: 11_990_000,
- registry_premium_pricing: None,
- },
- contacts: Some(types::DomainContactsCreateV2 {
- registrant: Some(types::ContactDomainCreate {
- address_mailing: types::Address {
- address1: "1 A St".to_string(),
- address2: None,
- city: "Tempe".to_string(),
- country: types::AddressCountry::Us,
- postal_code: "85281".to_string(),
- state: "AZ".to_string(),
- },
- email: "a@example.com".to_string(),
- encoding: types::ContactDomainCreateEncoding::Ascii,
- fax: None,
- job_title: None,
- metadata: Default::default(),
- name_first: "Ada".to_string(),
- name_last: "Lovelace".to_string(),
- name_middle: None,
- organization: None,
- phone: "+1.4805551212".to_string(),
- }),
- registrant_id: None,
- admin: None,
- admin_id: None,
- billing: None,
- billing_id: None,
- tech: None,
- tech_id: None,
- }),
- metadata: Default::default(),
- name_servers: vec![],
- period: std::num::NonZeroU64::new(1).expect("nonzero period"),
- privacy: false,
- renew_auto: true,
- })
+ .record_replace_type_name()
+ .domain("example.com")
+ .type_("A")
+ .name("www")
+ .body(vec![types::V1dnsRecordCreateTypeName {
+ data: "5.6.7.8".to_string(),
+ port: None,
+ priority: None,
+ protocol: None,
+ service: None,
+ ttl: Some(600),
+ weight: None,
+ }])
.send()
.await
- .expect("202 accepted");
+ .expect("request succeeds");
mock.assert_async().await;
}
#[tokio::test]
- async fn get_returns_domain_detail_as_free_form_json() {
- // `domain get` reads one domain's details. The response is decoded
- // free-form (a serde_json map) so it tolerates sparse/privacy-masked
- // contacts the typed DomainDetail would reject; the command emits it
- // as-is. Guards the `{domain}` path segment and the free-form decode.
+ async fn v1_record_delete_type_name_issues_delete() {
let server = MockServer::start_async().await;
let mock = server
.mock_async(|when, then| {
- when.method(GET).path("/v1/domains/example.com");
- then.status(200).json_body(json!({
- "domain": "example.com",
- "domainId": 12345,
- "status": "ACTIVE",
- "expires": "2027-06-18T00:00:00.000Z",
- "renewAuto": true,
- "nameServers": ["ns1.example.net", "ns2.example.net"]
- }));
+ when.method(DELETE)
+ .path("/v1/domains/example.com/records/A/www");
+ then.status(204);
})
.await;
- let detail = client_for(&server)
- .get()
+ client_for(&server)
+ .record_delete_type_name()
.domain("example.com")
+ .type_("A")
+ .name("www")
.send()
.await
- .expect("request succeeds")
- .into_inner();
+ .expect("request succeeds");
mock.assert_async().await;
- assert_eq!(
- detail.get("domain").and_then(|v| v.as_str()),
- Some("example.com")
- );
- assert_eq!(
- detail.get("status").and_then(|v| v.as_str()),
- Some("ACTIVE")
- );
}
}
diff --git a/rust/src/application/commands/mod.rs b/rust/src/application/commands/mod.rs
index ec7b7e8..ae9c511 100644
--- a/rust/src/application/commands/mod.rs
+++ b/rust/src/application/commands/mod.rs
@@ -6,6 +6,10 @@ use serde_json::json;
use crate::application::client::{ApplicationClient, api_url_for_env};
use crate::output_schema::output_schema;
+// App-registry mutations declare their scopes so `apps.app-registry:write` is
+// requested on demand (OAuth step-up), not granted at every login — it's a
+// rarely-used operation for most customers.
+use crate::scopes::{APP_REGISTRY_READ, APP_REGISTRY_WRITE};
output_schema!(ApplicationSummary {
"id": "string";
@@ -224,6 +228,7 @@ fn init_command() -> RuntimeCommandSpec {
)
.with_system("applications")
.with_tier(Tier::Mutate)
+ .with_scopes(&[APP_REGISTRY_READ, APP_REGISTRY_WRITE])
.with_output_schema::()
.with_arg(
clap::Arg::new("name")
@@ -388,6 +393,7 @@ fn update_command() -> RuntimeCommandSpec {
)
.with_system("applications")
.with_tier(Tier::Mutate)
+ .with_scopes(&[APP_REGISTRY_READ, APP_REGISTRY_WRITE])
.with_output_schema::()
.with_arg(
clap::Arg::new("id")
@@ -468,6 +474,7 @@ fn enable_command() -> RuntimeCommandSpec {
)
.with_system("applications")
.with_tier(Tier::Mutate)
+ .with_scopes(&[APP_REGISTRY_READ, APP_REGISTRY_WRITE])
.with_output_schema::()
.with_arg(
clap::Arg::new("name")
@@ -540,6 +547,7 @@ fn disable_command() -> RuntimeCommandSpec {
)
.with_system("applications")
.with_tier(Tier::Mutate)
+ .with_scopes(&[APP_REGISTRY_READ, APP_REGISTRY_WRITE])
.with_output_schema::()
.with_arg(
clap::Arg::new("name")
@@ -614,6 +622,7 @@ fn archive_command() -> RuntimeCommandSpec {
)
.with_system("applications")
.with_tier(Tier::Destructive)
+ .with_scopes(&[APP_REGISTRY_READ, APP_REGISTRY_WRITE])
.with_output_schema::()
.with_arg(
clap::Arg::new("name")
@@ -655,6 +664,7 @@ fn release_command() -> RuntimeCommandSpec {
)
.with_system("applications")
.with_tier(Tier::Mutate)
+ .with_scopes(&[APP_REGISTRY_READ, APP_REGISTRY_WRITE])
.with_output_schema::()
.with_arg(
clap::Arg::new("application-id")
@@ -712,6 +722,7 @@ fn deploy_command() -> RuntimeCommandSpec {
)
.with_system("applications")
.with_tier(Tier::Mutate)
+ .with_scopes(&[APP_REGISTRY_READ, APP_REGISTRY_WRITE])
.with_arg(
clap::Arg::new("name")
.long("name")
diff --git a/rust/src/auth.rs b/rust/src/auth.rs
index ebdb4ff..8580433 100644
--- a/rust/src/auth.rs
+++ b/rust/src/auth.rs
@@ -129,152 +129,3 @@ impl AuthProvider for GoDaddyAuthProvider {
Ok(envs)
}
}
-
-/// Stored in [`Credential::provider`] for the sso-key bypass path, so the domain
-/// client selects the `sso-key` Authorization scheme instead of Bearer.
-pub const SSO_KEY_PROVIDER: &str = "sso-key";
-
-/// Auth provider that composes [`GoDaddyAuthProvider`] (OAuth/PKCE) but, for
-/// `domain:*` commands whose target environment has an sso-key configured,
-/// returns that key instead.
-///
-/// The GoDaddy Domains API endpoints accept either an sso-key
-/// (`Authorization: sso-key :`) or an OAuth bearer token. This
-/// provider uses the sso-key only when one is configured for a `domain:*`
-/// command's environment; every other command — and any domain command without a
-/// configured key — uses OAuth (including scope step-up). Scoping the bypass to
-/// `domain:*` keeps it from affecting unrelated commands.
-#[derive(Debug, Default)]
-pub struct CompositeAuthProvider {
- oauth: GoDaddyAuthProvider,
-}
-
-impl CompositeAuthProvider {
- pub fn new() -> Self {
- Self {
- oauth: GoDaddyAuthProvider::new(),
- }
- }
-
- /// Build an sso-key credential, if this is a `domain:*` command and both a
- /// key and secret are present. Pure (no process/config access) for testing.
- fn sso_key_credential_from(
- env: &str,
- command: &str,
- key: Option<&str>,
- secret: Option<&str>,
- ) -> Option {
- if !command.starts_with("domain:") {
- return None;
- }
- let key = key.map(str::trim).filter(|s| !s.is_empty())?;
- let secret = secret.map(str::trim).filter(|s| !s.is_empty())?;
- Some(Credential {
- token: format!("{key}:{secret}"),
- provider: SSO_KEY_PROVIDER.to_owned(),
- env: env.to_owned(),
- ..Default::default()
- })
- }
-
- /// Resolve the sso-key for a domain command from the environment's config
- /// (`_API_KEY`/`_API_SECRET` env vars or the `environments.toml`
- /// entry) and turn it into a credential.
- fn sso_key_credential(env: &str, command: &str) -> Option {
- let domains = environments::resolve_domains(env).ok()?;
- Self::sso_key_credential_from(
- env,
- command,
- domains.api_key.as_deref(),
- domains.api_secret.as_deref(),
- )
- }
-}
-
-#[async_trait]
-impl AuthProvider for CompositeAuthProvider {
- fn name(&self) -> &str {
- self.oauth.name()
- }
-
- async fn get_credential(&self, env: &str, command: &str, tier: &str) -> Result {
- if let Some(cred) = Self::sso_key_credential(env, command) {
- return Ok(cred);
- }
- self.oauth.get_credential(env, command, tier).await
- }
-
- async fn get_credential_for(&self, req: &CredentialRequest<'_>) -> Result {
- if let Some(cred) = Self::sso_key_credential(req.env, req.command) {
- return Ok(cred);
- }
- self.oauth.get_credential_for(req).await
- }
-
- async fn status(&self, env: &str) -> Result {
- self.oauth.status(env).await
- }
-
- async fn logout(&self, env: &str) -> Result<()> {
- self.oauth.logout(env).await
- }
-
- async fn list_environments(&self) -> Result> {
- self.oauth.list_environments().await
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn sso_key_only_for_domain_commands_with_key_and_secret() {
- // domain command + both key/secret -> sso-key credential.
- let cred = CompositeAuthProvider::sso_key_credential_from(
- "ote",
- "domain:available",
- Some("KEY"),
- Some("SECRET"),
- )
- .expect("sso-key credential");
- assert_eq!(cred.token, "KEY:SECRET");
- assert_eq!(cred.provider, SSO_KEY_PROVIDER);
- assert_eq!(cred.env, "ote");
- }
-
- #[test]
- fn no_sso_key_for_non_domain_commands() {
- assert!(
- CompositeAuthProvider::sso_key_credential_from(
- "ote",
- "application:list",
- Some("KEY"),
- Some("SECRET"),
- )
- .is_none()
- );
- }
-
- #[test]
- fn no_sso_key_when_key_or_secret_missing_or_blank() {
- assert!(
- CompositeAuthProvider::sso_key_credential_from(
- "ote",
- "domain:suggest",
- Some("KEY"),
- None
- )
- .is_none()
- );
- assert!(
- CompositeAuthProvider::sso_key_credential_from(
- "ote",
- "domain:suggest",
- Some(" "),
- Some("SECRET"),
- )
- .is_none()
- );
- }
-}
diff --git a/rust/src/contacts/mod.rs b/rust/src/contacts/mod.rs
index ab71edf..63eb718 100644
--- a/rust/src/contacts/mod.rs
+++ b/rust/src/contacts/mod.rs
@@ -45,12 +45,15 @@ pub struct ContactsFile {
pub tech: Option,
}
-/// One contact's details. Field names mirror the Domains API `Contact`/`Address`
-/// schema (in snake_case), so a complete entry maps directly to a request
-/// contact. The fields the API requires (`name_first`/`name_last`/`email`/`phone`
-/// and the mailing-address `address1`/`city`/`state`/`postal_code`/`country`) are
-/// mandatory here too — a partial entry fails to parse rather than producing an
-/// invalid request.
+/// One contact's details. Field names mirror the contact/address fields, so a
+/// complete entry maps directly to a v3 request contact. The fields v3 requires
+/// (`name_first`/`name_last`/`email`/`phone` and the address
+/// `address1`/`city`/`state`/`postal_code`/`country`) are mandatory here too — a
+/// partial entry fails to parse rather than producing an invalid request.
+///
+/// v3's `Contact` has no middle name, job title, or fax; a `name_middle`/
+/// `job_title`/`fax` key left over from a v1/v2-era file is accepted (serde
+/// ignores unknown keys) but not sent.
#[derive(Debug, Clone, Deserialize)]
pub struct Contact {
pub name_first: String,
@@ -58,13 +61,7 @@ pub struct Contact {
pub email: String,
pub phone: String,
#[serde(default)]
- pub name_middle: Option,
- #[serde(default)]
pub organization: Option,
- #[serde(default)]
- pub job_title: Option,
- #[serde(default)]
- pub fax: Option,
pub address1: String,
#[serde(default)]
pub address2: Option,
@@ -96,80 +93,58 @@ impl Role {
}
impl Contact {
- /// Convert to the v2 API contact (`ContactDomainCreate`), validating the
- /// country code. Returns a human-readable error (surfaced by
- /// `domain purchase`) when the country is not a recognized two-letter ISO
- /// code. `encoding` is reported honestly: `ASCII` when every field is ASCII,
- /// else `UTF-8`, so accented names/addresses aren't mislabeled.
- pub fn to_api(&self, role: Role) -> Result {
+ /// Convert to the v3 API contact (`Contact`), validating the country code.
+ /// Returns a human-readable error (surfaced by `domain purchase`/`quote`) when
+ /// the country isn't shaped like an ISO-3166 alpha-2 code (see
+ /// [`validate_country`] — a format check, not list membership) or the phone
+ /// can't be parsed.
+ ///
+ /// v3's `Contact` is leaner than v2's: it has no middle name, job title, fax,
+ /// or character-encoding field, and the phone is a structured object
+ /// (`countryCode`/`nationalNumber`) rather than a dotted `+1.4805551212`
+ /// string. `name_middle`/`job_title`/`fax` are not part of the struct schema;
+ /// a leftover such key in a v1/v2-era `contacts.toml` is simply ignored (serde
+ /// skips unknown keys).
+ pub fn to_api(&self, role: Role) -> Result {
let country_upper = self.country.to_ascii_uppercase();
- let country = api::AddressCountry::try_from(country_upper.as_str()).map_err(|_| {
- format!(
- "{} contact in contacts.toml has invalid country {:?} \
- (expected a two-letter ISO code, e.g. US)",
- role.label(),
- self.country,
- )
- })?;
- let encoding = if self.is_all_ascii() {
- api::ContactDomainCreateEncoding::Ascii
- } else {
- api::ContactDomainCreateEncoding::Utf8
- };
- // Normalize phone (required) and fax (optional, only when present) to the
- // API's `+.` format so common inputs aren't rejected with a
- // cryptic 422.
- let phone = normalize_phone(&self.phone, &country_upper, "phone", role)?;
- let fax = match empty_to_none(&self.fax) {
- Some(f) => Some(normalize_phone(&f, &country_upper, "fax", role)?),
- None => None,
- };
- Ok(api::ContactDomainCreate {
- address_mailing: api::Address {
- address1: self.address1.clone(),
- address2: empty_to_none(&self.address2),
+ validate_country(&country_upper, role)?;
+ let phone = to_api_phone(&self.phone, &country_upper, role)?;
+ Ok(api::Contact {
+ address: api::SimpleAddress {
+ line1: self.address1.clone(),
+ line2: empty_to_none(&self.address2),
city: self.city.clone(),
- country,
- postal_code: self.postal_code.clone(),
- state: self.state.clone(),
+ state: Some(self.state.clone()),
+ postal_code: Some(self.postal_code.clone()),
+ country_code: api::CountryCode(country_upper),
},
- email: self.email.clone(),
- encoding,
- fax,
- job_title: empty_to_none(&self.job_title),
- metadata: Default::default(),
- name_first: self.name_first.clone(),
- name_last: self.name_last.clone(),
- name_middle: empty_to_none(&self.name_middle),
+ email: api::EmailAddress(self.email.clone()),
+ first_name: self.name_first.clone(),
+ last_name: self.name_last.clone(),
organization: empty_to_none(&self.organization),
phone,
})
}
+}
- /// Whether every populated field is ASCII (drives the `encoding` we report).
- fn is_all_ascii(&self) -> bool {
- let required = [
- &self.name_first,
- &self.name_last,
- &self.email,
- &self.phone,
- &self.address1,
- &self.city,
- &self.state,
- &self.postal_code,
- &self.country,
- ];
- let optional = [
- &self.name_middle,
- &self.organization,
- &self.job_title,
- &self.fax,
- &self.address2,
- ];
- required.iter().all(|s| s.is_ascii())
- && optional
- .iter()
- .all(|o| o.as_deref().is_none_or(str::is_ascii))
+/// Validate a (already upper-cased) country code has the *shape* of an ISO-3166
+/// alpha-2 code — two ASCII uppercase letters (or the special `C2`). This is a
+/// cheap format check, not membership in the ISO-3166 list (e.g. `ZZ` passes);
+/// the API validates the actual code server-side. It preserves the clear early
+/// error the v2 path gave before the strict `AddressCountry` enum was dropped
+/// from the generated client.
+fn validate_country(country_upper: &str, role: Role) -> Result<(), String> {
+ let ok = country_upper == "C2"
+ || (country_upper.len() == 2 && country_upper.bytes().all(|b| b.is_ascii_uppercase()));
+ if ok {
+ Ok(())
+ } else {
+ Err(format!(
+ "{} contact in contacts.toml has invalid country {:?} \
+ (expected a two-letter ISO code, e.g. US)",
+ role.label(),
+ country_upper,
+ ))
}
}
@@ -187,30 +162,33 @@ fn empty_to_none(value: &Option) -> Option {
.map(str::to_owned)
}
-/// Normalize a user-entered phone or fax number to the Domains API format
-/// `+.` (e.g. `+1.4805551212`), matching the
-/// API's required pattern `^\+([0-9]){1,3}\.([0-9] ?){5,14}$`.
+/// Parse a user-entered phone number into v3's structured `Phone`
+/// (`countryCode` = the calling code digits, e.g. `44`; `nationalNumber` = the
+/// national digits, e.g. `7793601890`).
///
/// `region` is the contact's two-letter ISO country, used as the default region so
/// a number typed without a `+` country prefix (e.g. a local `07793 601890`) still
/// parses. Returns a human-readable error (surfaced by `domain purchase`, like the
/// invalid-country error) when the value can't be parsed as a phone number, rather
/// than forwarding it and letting the API reject it opaquely.
-fn normalize_phone(raw: &str, region: &str, field: &str, role: Role) -> Result {
+fn to_api_phone(raw: &str, region: &str, role: Role) -> Result {
let region_id = region.parse::().ok();
let number = phonenumber::parse(region_id, raw).map_err(|_| {
format!(
- "{} contact in contacts.toml has an unrecognized {} {:?} \
+ "{} contact in contacts.toml has an unrecognized phone {:?} \
(expected a phone number like +1.4805551212)",
role.label(),
- field,
raw,
)
})?;
// `NationalNumber`'s Display preserves significant leading zeros (e.g. Italy),
// so this is correct for every region, not just those whose national number is
// a plain integer.
- Ok(format!("+{}.{}", number.code().value(), number.national()))
+ Ok(api::Phone {
+ country_code: Some(number.code().value().to_string()),
+ national_number: Some(number.national().to_string()),
+ extension_number: None,
+ })
}
impl ContactsFile {
@@ -224,10 +202,10 @@ impl ContactsFile {
}
}
- /// The v2 API contact for a role: `None` when the role is absent (→ omit
+ /// The v3 API contact for a role: `None` when the role is absent (→ omit
/// from the request → account default), or an error when the configured
- /// country is invalid.
- pub fn to_api(&self, role: Role) -> Result