From 49189d90eac02b2fd47fd8378e2c800a0fa8e65b Mon Sep 17 00:00:00 2001 From: Jacob Page Date: Fri, 17 Jul 2026 10:01:26 -0700 Subject: [PATCH 1/4] fix(api-explorer): remove broken --query flag, add domains catalog source (DEVEX-898) `gddy api call --query` silently nulled the response body on any lookup miss instead of extracting the requested field, and duplicated the global `--expr`/`--filter` JMESPath flags cli-engine already provides on every command. Remove the local flag and hand-rolled path parser in favor of the global flags, which are strictly more capable and operate on the same printed envelope. Also wire domains-client's vendored OpenAPI spec in as a new "domains" local catalog source (same mechanism already used for hosting-nodejs), so `gddy api domain/endpoint/describe/call` can browse and call the Domains registrar API for the first time, using the same spec that already drives the typed domains-client crate. Co-Authored-By: Claude Sonnet 5 --- rust/api-catalog-sources.json | 9 +- rust/schemas/api/domains.json | 3000 ++++++++++++++++++++++++++++++++ rust/schemas/api/manifest.json | 5 + rust/src/api_explorer/mod.rs | 54 +- 4 files changed, 3018 insertions(+), 50 deletions(-) create mode 100644 rust/schemas/api/domains.json diff --git a/rust/api-catalog-sources.json b/rust/api-catalog-sources.json index 6fcccde..fae4792 100644 --- a/rust/api-catalog-sources.json +++ b/rust/api-catalog-sources.json @@ -86,14 +86,19 @@ { "domain": "hosting-nodejs", "path": "schemas/openapi/hosting-nodejs-public-v1.yaml" + }, + { + "domain": "domains", + "path": "domains-client/openapi/domains.oas3.json" } ], "legacyTypescript": { "status": "retired-on-rust-port", "sharedDomainCount": 20, "rustOnlyDomains": [ - "hosting-nodejs" + "hosting-nodejs", + "domains" ], - "rationale": "The legacy TypeScript catalog contains the 20 remote domains above. hosting-nodejs is intentionally Rust-only because it is generated from the CLI's vendored public hosting specification." + "rationale": "The legacy TypeScript catalog contains the 20 remote domains above. hosting-nodejs and domains are intentionally Rust-only: each is generated from a vendored, manually-curated local spec (hosting-nodejs's public OpenAPI doc; domains' merged v1+v3 spec, which is also progenitor's source of truth for the typed domains-client crate)." } } diff --git a/rust/schemas/api/domains.json b/rust/schemas/api/domains.json new file mode 100644 index 0000000..cd3e8a8 --- /dev/null +++ b/rust/schemas/api/domains.json @@ -0,0 +1,3000 @@ +{ + "$defs": { + "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", + "properties": { + "agreementType": { + "allOf": [ + { + "$ref": "#/$defs/AgreementType" + } + ], + "description": "The type of legal agreement. Identifies which agreement text the customer must accept.\n", + "example": "DNRA" + }, + "title": { + "description": "Human-readable title of the agreement, suitable for display to the customer.\n", + "example": "Domain Name Registration Agreement", + "type": "string" + }, + "url": { + "description": "URL to the full legal text of this agreement. Present when available.", + "example": "https://www.godaddy.com/agreements/showdoc?pageid=reg_sa", + "format": "uri", + "type": "string" + } + }, + "title": "Agreement", + "type": "object" + }, + "AgreementType": { + "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.\n", + "title": "Agreement Type", + "type": "string" + }, + "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", + "properties": { + "available": { + "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", + "type": "boolean" + }, + "definitive": { + "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", + "type": "boolean" + }, + "domain": { + "description": "The domain name checked, normalized to punycode A-label form.\n", + "example": "example.com", + "type": "string" + }, + "error": { + "allOf": [ + { + "$ref": "#/$defs/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 — 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.\n" + }, + "inventory": { + "allOf": [ + { + "$ref": "#/$defs/InventoryType" + } + ], + "description": "The inventory source for this domain. Present when available is true and pricing fields are returned.\n", + "example": "REGISTRY" + }, + "prices": { + "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": "#/$defs/TermPrice" + }, + "nullable": true, + "type": "array" + }, + "unicodeDomain": { + "description": "The Unicode (U-label) form of the domain. Present only for IDN domains.\n", + "example": "münchen.de", + "type": "string" + } + }, + "title": "Availability", + "type": "object" + }, + "AvailabilityCheckCriteria": { + "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.\n", + "properties": { + "domains": { + "description": "List of 1–50 domain names to check, in punycode A-label form for IDNs.\n", + "example": [ + "example.com", + "example.net" + ], + "items": { + "type": "string" + }, + "maxItems": 50, + "minItems": 1, + "type": "array" + }, + "iscCode": { + "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", + "type": "string" + }, + "optimizeFor": { + "allOf": [ + { + "$ref": "#/$defs/OptimizationTarget" + } + ], + "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" + } + }, + "required": [ + "domains" + ], + "title": "Availability Check Criteria", + "type": "object" + }, + "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", + "properties": { + "agreedAt": { + "allOf": [ + { + "$ref": "#/$defs/date-time" + } + ], + "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" + }, + "agreedBy": { + "$ref": "#/$defs/ConsentActor" + }, + "agreementTypes": { + "description": "The agreement types the customer accepted. Must match the agreementType values returned in the corresponding quote's requiredAgreements array.\n", + "example": [ + "DNRA" + ], + "items": { + "$ref": "#/$defs/AgreementType" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "agreementTypes", + "agreedAt", + "agreedBy" + ], + "title": "Consent", + "type": "object" + }, + "ConsentActor": { + "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", + "properties": { + "actor": { + "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", + "type": "string" + }, + "ip": { + "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.\n", + "example": "203.0.113.7", + "type": "string" + }, + "principal": { + "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", + "type": "string" + }, + "type": { + "allOf": [ + { + "$ref": "#/$defs/ConsentActorType" + } + ], + "description": "Who transmitted consent relative to the principal.\n", + "example": "DIRECT" + } + }, + "required": [ + "type", + "principal" + ], + "title": "Consent Actor", + "type": "object", + "x-sensitivity": "confidential" + }, + "ConsentActorType": { + "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.\n", + "title": "Consent Actor Type", + "type": "string" + }, + "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", + "properties": { + "address": { + "allOf": [ + { + "$ref": "#/$defs/simple-address" + } + ], + "description": "The contact's mailing address for WHOIS and ICANN records.\n", + "x-sensitivity": "confidential" + }, + "email": { + "allOf": [ + { + "$ref": "#/$defs/email-address" + } + ], + "description": "The contact's email address. Used for registry WHOIS and renewal notifications.\n", + "example": "foo@bar.com", + "x-sensitivity": "confidential" + }, + "firstName": { + "description": "The contact's first (given) name.", + "example": "Jane", + "type": "string", + "x-sensitivity": "confidential" + }, + "lastName": { + "description": "The contact's last (family) name.", + "example": "Smith", + "type": "string", + "x-sensitivity": "confidential" + }, + "organization": { + "description": "Organization or company name. Required for contacts acting on behalf of a legal entity. Leave blank for individual registrants.\n", + "example": "Example LLC", + "type": "string", + "x-sensitivity": "confidential" + }, + "phone": { + "allOf": [ + { + "$ref": "#/$defs/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" + } + }, + "required": [ + "firstName", + "lastName", + "email", + "phone", + "address" + ], + "title": "Contact", + "type": "object", + "x-sensitivity": "confidential" + }, + "ContactSource": { + "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).\n", + "enum": [ + "INLINE", + "PROFILE", + "ACCOUNT" + ], + "title": "Contact Source", + "type": "string" + }, + "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", + "properties": { + "admin": { + "allOf": [ + { + "$ref": "#/$defs/Contact" + } + ], + "description": "The administrative contact, responsible for managing the domain on behalf of the registrant. Cascades from registrant when omitted.\n" + }, + "billing": { + "allOf": [ + { + "$ref": "#/$defs/Contact" + } + ], + "description": "The billing contact, receives invoices and renewal notices. Cascades from registrant when omitted.\n" + }, + "registrant": { + "allOf": [ + { + "$ref": "#/$defs/Contact" + } + ], + "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" + }, + "tech": { + "allOf": [ + { + "$ref": "#/$defs/Contact" + } + ], + "description": "The technical contact, responsible for DNS and nameserver configuration. Cascades from registrant when omitted.\n" + } + }, + "required": [ + "registrant" + ], + "title": "Contacts", + "type": "object", + "x-sensitivity": "confidential" + }, + "DNSRecord": { + "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", + "properties": { + "data": { + "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", + "type": "string" + }, + "flag": { + "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, + "maximum": 255, + "minimum": 0, + "type": "integer" + }, + "name": { + "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": "string" + }, + "port": { + "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, + "maximum": 65535, + "minimum": 0, + "type": "integer" + }, + "priority": { + "description": "Record priority. Required for MX and SRV records. Lower values indicate higher preference. Omit for all other record types.\n", + "example": 10, + "maximum": 65535, + "minimum": 0, + "type": "integer" + }, + "protocol": { + "description": "Transport protocol for SRV and TLSA records, prefixed with an underscore. Typically `_tcp` or `_udp`.\n", + "example": "_tcp", + "type": "string" + }, + "recordId": { + "description": "Server-assigned stable identifier for this DNS record.\n", + "readOnly": true, + "type": "string" + }, + "service": { + "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", + "type": "string" + }, + "tag": { + "description": "CAA record property tag. Common values: `issue` (authorize CA to issue), `issuewild` (wildcard certs), `iodef` (violation reporting URL).\n", + "example": "issue", + "type": "string" + }, + "ttl": { + "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, + "maximum": 86400, + "minimum": 600, + "type": "integer" + }, + "type": { + "allOf": [ + { + "$ref": "#/$defs/DNSRecordType" + } + ], + "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" + }, + "weight": { + "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, + "maximum": 65535, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "name", + "type", + "data", + "ttl" + ], + "title": "DNS Record", + "type": "object" + }, + "DNSRecordType": { + "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.\n", + "title": "DNS Record Type", + "type": "string" + }, + "DNSRecords": { + "description": "A paginated collection of DNS resource records within a zone. Supports filtering by record type and name, field projection, and page-based pagination. When totalRequired=true, totalItems and totalPages are included for non-empty result sets; both are omitted when there are zero matching records.\n", + "properties": { + "items": { + "description": "DNS records for the current page.", + "items": { + "$ref": "#/$defs/DNSRecord" + }, + "nullable": true, + "type": "array" + }, + "links": { + "description": "HATEOAS pagination links for navigating result pages. May include rel=self (current page), rel=first, rel=last, rel=next, rel=prev — included when applicable.\n", + "items": { + "$ref": "#/$defs/link-description" + }, + "nullable": true, + "readOnly": true, + "type": "array" + }, + "totalItems": { + "description": "Total number of records matching the current filter across all pages. Present only when totalRequired=true and at least one record matches.\n", + "example": 150, + "minimum": 1, + "type": "integer" + }, + "totalPages": { + "description": "Total number of pages available at the requested pageSize. A non-negative, non-zero integer per platform pagination standards. Present only when totalRequired=true and at least one record matches; omitted when the result set is empty.\n", + "example": 6, + "minimum": 1, + "type": "integer" + } + }, + "title": "DNS Records", + "type": "object" + }, + "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", + "properties": { + "autoRenew": { + "description": "Whether this domain is set to auto-renew before expiry. When true, the registration is renewed automatically before it expires.\n", + "type": "boolean" + }, + "createdAt": { + "allOf": [ + { + "$ref": "#/$defs/date-time" + } + ], + "description": "The date and time when this domain was first registered with GoDaddy.\n", + "readOnly": true + }, + "domain": { + "description": "The registered domain name in punycode A-label form. For IDNs, the Unicode (U-label) form is available in the idnDomain field.\n", + "example": "example.com", + "readOnly": true, + "type": "string" + }, + "expiresAt": { + "allOf": [ + { + "$ref": "#/$defs/date-time" + } + ], + "description": "The date and time when this domain registration expires. After expiry the domain enters a grace period before deletion.\n", + "readOnly": true + }, + "idnDomain": { + "description": "The Unicode (U-label) representation of the domain. Present only for internationalized domain names (IDNs).\n", + "readOnly": true, + "type": "string" + }, + "links": { + "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.\n", + "items": { + "$ref": "#/$defs/link-description" + }, + "nullable": true, + "readOnly": true, + "type": "array" + }, + "nameServers": { + "allOf": [ + { + "$ref": "#/$defs/NameServers" + } + ], + "description": "The current authoritative nameservers for this domain.\n", + "example": [ + "ns01.domaincontrol.com", + "ns02.domaincontrol.com" + ] + }, + "privacy": { + "description": "Whether WHOIS privacy protection is active for this domain. When true, the registrant's contact details are masked in public WHOIS.\n", + "type": "boolean" + }, + "renewBy": { + "allOf": [ + { + "$ref": "#/$defs/date-time" + } + ], + "description": "The date and time when this domain must renew on before entering expiry.\n", + "readOnly": true + }, + "status": { + "allOf": [ + { + "$ref": "#/$defs/DomainStatus" + } + ], + "description": "The current lifecycle status of this domain.\n", + "readOnly": true + }, + "transferLock": { + "description": "Whether the transfer lock (registrar lock / EPP lock) is active. When true, outbound transfers to another registrar are blocked.\n", + "readOnly": true, + "type": "boolean" + }, + "updatedAt": { + "allOf": [ + { + "$ref": "#/$defs/date-time" + } + ], + "description": "The date and time when this domain was last updated\n", + "readOnly": true + } + }, + "title": "Domain", + "type": "object" + }, + "DomainOperation": { + "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", + "properties": { + "createdAt": { + "allOf": [ + { + "$ref": "#/$defs/date-time" + } + ], + "description": "Timestamp when this operation was created.", + "readOnly": true + }, + "domain": { + "description": "The domain name this operation applies to.", + "example": "example.com", + "readOnly": true, + "type": "string" + }, + "error": { + "allOf": [ + { + "$ref": "#/$defs/error" + } + ], + "description": "Present when status is FAILED. Absent while non-terminal and when status is COMPLETED.\n", + "readOnly": true + }, + "links": { + "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.\n", + "items": { + "$ref": "#/$defs/link-description" + }, + "nullable": true, + "readOnly": true, + "type": "array" + }, + "operationId": { + "allOf": [ + { + "$ref": "#/$defs/uuid" + } + ], + "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", + "example": "9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c", + "readOnly": true + }, + "result": { + "allOf": [ + { + "$ref": "#/$defs/DomainOperationResult" + } + ], + "description": "Present when status is COMPLETED. Absent while non-terminal and when status is FAILED.\n", + "readOnly": true + }, + "status": { + "allOf": [ + { + "$ref": "#/$defs/DomainOperationStatus" + } + ], + "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 + }, + "type": { + "allOf": [ + { + "$ref": "#/$defs/DomainOperationType" + } + ], + "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).\n", + "readOnly": true + }, + "updatedAt": { + "allOf": [ + { + "$ref": "#/$defs/date-time" + } + ], + "description": "Timestamp of the most recent status update.", + "readOnly": true + } + }, + "title": "Domain Operation", + "type": "object" + }, + "DomainOperationResult": { + "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 — expiresAt, orderId.\n", + "properties": { + "expiresAt": { + "allOf": [ + { + "$ref": "#/$defs/date-time" + } + ], + "description": "New domain expiry date. Present for REGISTER and RENEW operations.\n" + }, + "orderId": { + "description": "The commerce order ID associated with the charge. Present for commercial operations (REGISTER, RENEW, TRANSFER_IN).\n", + "example": "ord_abc123", + "type": "string" + }, + "updatedAt": { + "allOf": [ + { + "$ref": "#/$defs/date-time" + } + ], + "description": "Timestamp of the completed non-commercial mutation.\n" + } + }, + "readOnly": true, + "title": "Domain Operation Result", + "type": "object" + }, + "DomainOperationStatus": { + "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.\n", + "title": "Domain Operation Status", + "type": "string" + }, + "DomainOperationType": { + "description": "The type of asynchronous domain operation. Used to distinguish which workflow is being polled on the /operations/{operationId} endpoint. REGISTER — new domain registration.\n", + "title": "Domain Operation Type", + "type": "string" + }, + "DomainStatus": { + "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.\n", + "enum": [ + "ACTIVE", + "CANCELLED", + "DELETED_REDEEMABLE", + "EXPIRED", + "FAILED", + "HELD_REGISTRAR", + "LOCKED_REGISTRAR", + "OWNERSHIP_CHANGED", + "PARKED", + "PENDING_REGISTRATION", + "PENDING_TRANSFER", + "REPOSSESSED", + "SUSPENDED", + "TRANSFERRED" + ], + "title": "Domain Status", + "type": "string" + }, + "InlineRegistrationProfile": { + "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 — 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", + "properties": { + "autoRenew": { + "description": "Auto-renew preference for this registration. Omit to inherit from the resolved saved profile or account defaults.\n", + "type": "boolean" + }, + "contacts": { + "allOf": [ + { + "$ref": "#/$defs/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" + }, + "nameServers": { + "allOf": [ + { + "$ref": "#/$defs/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" + ] + }, + "privacy": { + "description": "WHOIS privacy preference for this registration. Omit to inherit from the resolved saved profile or account defaults.\n", + "type": "boolean" + } + }, + "title": "Inline Registration Profile", + "type": "object" + }, + "InventoryType": { + "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.\n", + "enum": [ + "REGISTRY", + "REGISTRY_PREMIUM", + "PREMIUM" + ], + "title": "Inventory Type", + "type": "string" + }, + "NameServers": { + "description": "Authoritative nameserver hostnames for a domain. ICANN requires a minimum of two nameservers; registries accept up to thirteen.\n", + "items": { + "$ref": "#/$defs/NameserverHostname" + }, + "maxItems": 13, + "minItems": 2, + "title": "Name Servers", + "type": "array" + }, + "NameserverHostname": { + "description": "A fully-qualified nameserver hostname in punycode A-label form. Labels are dot-separated; a trailing dot is not required.\n", + "title": "Nameserver Hostname", + "type": "string" + }, + "OptimizationTarget": { + "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).\n", + "enum": [ + "SPEED", + "ACCURACY" + ], + "title": "Optimization Target", + "type": "string" + }, + "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", + "properties": { + "consent": { + "allOf": [ + { + "$ref": "#/$defs/Consent" + } + ], + "description": "The customer's consent record for the legal agreements returned in the quote. Must reference the same agreementTypes as the quote.\n" + }, + "createdAt": { + "allOf": [ + { + "$ref": "#/$defs/date-time" + } + ], + "description": "Timestamp when this registration was initiated.", + "readOnly": true + }, + "domain": { + "description": "The domain name to register, in punycode A-label form for IDNs. Must match the domain in the quoteToken.\n", + "example": "example.com", + "type": "string" + }, + "expiresAt": { + "allOf": [ + { + "$ref": "#/$defs/date-time" + } + ], + "description": "The domain's expiry date once the registration completes. Present only when status is COMPLETED.\n", + "readOnly": true + }, + "links": { + "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.\n", + "items": { + "$ref": "#/$defs/link-description" + }, + "readOnly": true, + "type": "array" + }, + "operationId": { + "allOf": [ + { + "$ref": "#/$defs/uuid" + } + ], + "description": "Identifier of the DomainOperation tracking this registration. Same value returned by GET /operations/{operationId} for abstract operation tracking.\n", + "example": "9f1c2e7a-4b3d-4e8f-a1c2-3d4e5f6a7b8c", + "readOnly": true + }, + "period": { + "default": 1, + "description": "Registration period in years. Must match the period in the quote.", + "example": 1, + "maximum": 10, + "minimum": 1, + "type": "integer" + }, + "profile": { + "allOf": [ + { + "$ref": "#/$defs/InlineRegistrationProfile" + } + ], + "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" + }, + "profileId": { + "allOf": [ + { + "$ref": "#/$defs/uuid" + } + ], + "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" + }, + "quoteToken": { + "allOf": [ + { + "$ref": "#/$defs/uuid" + } + ], + "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", + "example": "7f3a2b1c-9d8e-4012-a5b6-c1d2e3f4a5b6", + "writeOnly": true + }, + "registrationId": { + "allOf": [ + { + "$ref": "#/$defs/uuid" + } + ], + "description": "Server-assigned stable identifier for this registration record.\n", + "readOnly": true + }, + "status": { + "allOf": [ + { + "$ref": "#/$defs/DomainOperationStatus" + } + ], + "description": "Current execution status of this registration.", + "readOnly": true + }, + "updatedAt": { + "allOf": [ + { + "$ref": "#/$defs/date-time" + } + ], + "description": "Timestamp of the most recent status update.", + "readOnly": true + } + }, + "required": [ + "domain", + "consent" + ], + "title": "Registration", + "type": "object" + }, + "RegistrationQuote": { + "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 — this is not an error; it means the domain cannot be registered as requested.\n", + "properties": { + "available": { + "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", + "type": "boolean" + }, + "domain": { + "description": "The domain name being quoted, in punycode A-label form for IDNs.\n", + "example": "example.com", + "type": "string" + }, + "expiresAt": { + "allOf": [ + { + "$ref": "#/$defs/date-time" + } + ], + "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" + }, + "irreversible": { + "description": "Whether executing this quote is irreversible once accepted. Use to calibrate the explicitness of any confirmation step presented before execute.\n", + "example": false, + "type": "boolean" + }, + "period": { + "description": "Registration period in years for which the price is quoted.", + "example": 1, + "maximum": 10, + "minimum": 1, + "type": "integer" + }, + "price": { + "allOf": [ + { + "$ref": "#/$defs/simple-money" + } + ], + "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" + }, + "quoteToken": { + "allOf": [ + { + "$ref": "#/$defs/uuid" + } + ], + "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" + }, + "renewalPrice": { + "allOf": [ + { + "$ref": "#/$defs/simple-money" + } + ], + "description": "Indicative renewal cost at current rates. Not a price guarantee; renewal pricing is locked at time of renewal quote.\n" + }, + "requiredAgreements": { + "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", + "items": { + "$ref": "#/$defs/Agreement" + }, + "nullable": true, + "type": "array" + }, + "resolved": { + "allOf": [ + { + "$ref": "#/$defs/ResolvedSettings" + } + ], + "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" + } + }, + "title": "Registration Quote", + "type": "object" + }, + "ResolvedSettings": { + "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.\n", + "properties": { + "autoRenew": { + "description": "The effective auto-renew setting that will be applied upon registration.\n", + "type": "boolean" + }, + "contactSource": { + "allOf": [ + { + "$ref": "#/$defs/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" + }, + "nameServers": { + "allOf": [ + { + "$ref": "#/$defs/NameServers" + } + ], + "description": "The effective nameservers that will be provisioned for the domain.\n", + "example": [ + "ns01.domaincontrol.com", + "ns02.domaincontrol.com" + ] + }, + "privacy": { + "description": "The effective WHOIS privacy setting that will be applied upon registration.\n", + "type": "boolean" + }, + "profileId": { + "allOf": [ + { + "$ref": "#/$defs/uuid" + } + ], + "description": "The saved registration profile that was applied, if any. Absent when the registrant was derived from account identity.\n", + "example": "14514a29-5fce-4624-8d8a-d8abd56015e2", + "readOnly": true + }, + "registrantSummary": { + "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", + "example": "Jane Smith / jane@example.com", + "type": "string", + "x-sensitivity": "confidential" + } + }, + "title": "Resolved Settings", + "type": "object" + }, + "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.\n", + "properties": { + "domain": { + "description": "The suggested domain name in punycode A-label form.\n", + "example": "sunrisebakery.com", + "type": "string" + }, + "inventory": { + "allOf": [ + { + "$ref": "#/$defs/InventoryType" + } + ], + "description": "The inventory source for this domain. Present when pricing fields are present.\n", + "example": "REGISTRY" + }, + "prices": { + "description": "Indicative pricing offered per registration term length, in ascending order of period. Prices are best-effort indicative and are locked only at quote time.\n", + "items": { + "$ref": "#/$defs/TermPrice" + }, + "nullable": true, + "type": "array" + } + }, + "title": "Suggestion", + "type": "object" + }, + "SuggestionSource": { + "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.\n", + "enum": [ + "CC_TLD", + "EXTENSION", + "KEYWORD_SPIN", + "PREMIUM" + ], + "title": "Suggestion Source", + "type": "string" + }, + "Term": { + "default": "YEAR", + "description": "The unit of measure for a registration period. YEAR — registration period expressed in whole years.\n", + "enum": [ + "YEAR" + ], + "title": "Term", + "type": "string" + }, + "TermPrice": { + "description": "Pricing for one registration term, including sale and list prices for the full term and optional renewal and first-term breakdowns.\n", + "properties": { + "period": { + "description": "The registration period length, in units of term.\n", + "example": 2, + "maximum": 10, + "minimum": 1, + "type": "integer" + }, + "price": { + "allOf": [ + { + "$ref": "#/$defs/simple-money" + } + ], + "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.\n" + }, + "renewalPrice": { + "allOf": [ + { + "$ref": "#/$defs/simple-money" + } + ], + "description": "Sale price that will apply when the domain renews, for the same term and period. Not a guarantee.\n" + }, + "term": { + "allOf": [ + { + "$ref": "#/$defs/Term" + } + ], + "description": "Unit in which period is expressed. Currently only YEAR is supported.\n", + "example": "YEAR" + } + }, + "title": "Term Price", + "type": "object" + }, + "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

\nNOTE: These are sample values, there are many\nmore", + "enum": [ + "AC", + "AD", + "AE", + "AF", + "AG", + "AI", + "AL", + "AM", + "AO", + "AQ", + "AR", + "AS", + "AT", + "AU", + "AW", + "AX", + "AZ", + "BA", + "BB", + "BD", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BM", + "BN", + "BO", + "BQ", + "BR", + "BS", + "BT", + "BV", + "BW", + "BY", + "BZ", + "CA", + "CC", + "CD", + "CF", + "CG", + "CH", + "CI", + "CK", + "CL", + "CM", + "CN", + "CO", + "CR", + "CV", + "CW", + "CX", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "EH", + "ER", + "ES", + "ET", + "FI", + "FJ", + "FK", + "FM", + "FO", + "FR", + "GA", + "GB", + "GD", + "GE", + "GF", + "GG", + "GH", + "GI", + "GL", + "GM", + "GN", + "GP", + "GQ", + "GR", + "GS", + "GT", + "GU", + "GW", + "GY", + "HK", + "HM", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IM", + "IN", + "IO", + "IQ", + "IS", + "IT", + "JE", + "JM", + "JO", + "JP", + "KE", + "KG", + "KH", + "KI", + "KM", + "KN", + "KR", + "KV", + "KW", + "KY", + "KZ", + "LA", + "LB", + "LC", + "LI", + "LK", + "LR", + "LS", + "LT", + "LU", + "LV", + "LY", + "MA", + "MC", + "MD", + "ME", + "MG", + "MH", + "MK", + "ML", + "MM", + "MN", + "MO", + "MP", + "MQ", + "MR", + "MS", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NC", + "NE", + "NF", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NU", + "NZ", + "OM", + "PA", + "PE", + "PF", + "PG", + "PH", + "PK", + "PL", + "PM", + "PN", + "PR", + "PS", + "PT", + "PW", + "PY", + "QA", + "RE", + "RO", + "RS", + "RU", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SH", + "SI", + "SJ", + "SK", + "SL", + "SM", + "SN", + "SO", + "SR", + "ST", + "SV", + "SX", + "SZ", + "TC", + "TD", + "TF", + "TG", + "TH", + "TJ", + "TK", + "TL", + "TM", + "TN", + "TO", + "TP", + "TR", + "TT", + "TV", + "TW", + "TZ", + "UA", + "UG", + "UM", + "US", + "UY", + "UZ", + "VA", + "VC", + "VE", + "VG", + "VI", + "VN", + "VU", + "WF", + "WS", + "YE", + "YT", + "ZA", + "ZM", + "ZW" + ], + "format": "iso-country-code", + "type": "string" + }, + "postalCode": { + "description": "Postal or zip code", + "format": "postal-code", + "type": "string" + }, + "state": { + "description": "State or province or territory", + "format": "state-province-territory", + "type": "string" + } + } + }, + "V1Contact": { + "properties": { + "addressMailing": { + "$ref": "#/$defs/V1Address" + }, + "email": { + "format": "email", + "type": "string" + }, + "fax": { + "format": "phone", + "type": "string" + }, + "jobTitle": { + "type": "string" + }, + "nameFirst": { + "format": "person-name", + "type": "string" + }, + "nameLast": { + "format": "person-name", + "type": "string" + }, + "nameMiddle": { + "type": "string" + }, + "organization": { + "format": "organization-name", + "type": "string" + }, + "phone": { + "format": "phone", + "type": "string" + } + } + }, + "V1DomainSummary": { + "properties": { + "authCode": { + "description": "Authorization code for transferring the Domain", + "type": "string" + }, + "contactAdmin": { + "$ref": "#/$defs/V1Contact" + }, + "contactBilling": { + "$ref": "#/$defs/V1Contact" + }, + "contactRegistrant": { + "$ref": "#/$defs/V1Contact" + }, + "contactTech": { + "$ref": "#/$defs/V1Contact" + }, + "createdAt": { + "description": "Date and time when this domain was created", + "type": "string" + }, + "deletedAt": { + "description": "Date and time when this domain was deleted", + "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" + }, + "nullable": true, + "type": "array" + }, + "privacy": { + "description": "Whether or not the domain has privacy protection", + "type": "boolean" + }, + "registrarCreatedAt": { + "description": "Date and time when this domain was created by the registrar", + "format": "iso-datetime", + "type": "string" + }, + "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
", + "type": "string" + }, + "transferAwayEligibleAt": { + "description": "Date and time when this domain is eligible to transfer", + "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" + } + } + }, + "country-code": { + "description": "A two-character ISO 3166-1 code that identifies the country or region.", + "type": "string" + }, + "currency-code": { + "description": "A three-character ISO-4217 currency code.", + "title": "Currency Code", + "type": "string" + }, + "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" + }, + "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" + }, + "error": { + "description": "The error information.", + "properties": { + "correlationId": { + "description": "Internal identifier used for correlation purposes.", + "type": "string" + }, + "details": { + "additionalItems": false, + "description": "An array of additional details about the error. Required for client-side `4XX` errors.", + "items": { + "$ref": "#/$defs/error-details" + }, + "nullable": true, + "type": "array" + }, + "informationLink": { + "description": "The URI for detailed information related to this error for the developer.", + "type": "string" + }, + "links": { + "description": "An array of error-related HATEOAS links.", + "items": { + "$ref": "#/$defs/link-description" + }, + "nullable": true, + "readOnly": true, + "type": "array" + }, + "message": { + "description": "The message that describes the error.", + "type": "string" + }, + "name": { + "description": "The human-readable, unique name of the error.", + "type": "string" + } + }, + "title": "Error", + "type": "object" + }, + "error-details": { + "description": "The error details. Required for client-side `4XX` errors.", + "properties": { + "description": { + "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.", + "type": "string" + }, + "field": { + "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).", + "type": "string" + }, + "issue": { + "description": "The unique fine-grained application-level error code.", + "type": "string" + }, + "location": { + "default": "body", + "description": "The location of the field that caused the error. Value is `body`, `path`, or `query`.", + "type": "string" + }, + "value": { + "description": "The value of the field that caused the error.", + "type": "string" + } + }, + "title": "Error Details", + "type": "object" + }, + "link-description": { + "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.", + "format": "uri", + "type": "string" + }, + "method": { + "description": "The method to use to request the link target. For example, for HTTP, this might be `GET` or `DELETE`.", + "type": "string" + }, + "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" + }, + "submissionMediaType": { + "default": "application/json", + "description": "The media type with which to submit data with the request.", + "type": "string" + }, + "submissionSchema": { + "description": "The schema that describes the request data." + }, + "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." + }, + "title": { + "description": "The link title.", + "type": "string" + } + }, + "title": "Link Description", + "type": "object" + }, + "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": { + "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).", + "type": "string" + }, + "extensionNumber": { + "description": "The extension number.", + "type": "string" + }, + "nationalNumber": { + "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).", + "type": "string" + } + }, + "title": "Phone", + "type": "object" + }, + "simple-address": { + "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": { + "city": { + "description": "The city name.", + "type": "string" + }, + "countryCode": { + "$ref": "#/$defs/country-code" + }, + "line1": { + "description": "The first line of the address. For example, number or street.", + "type": "string" + }, + "line2": { + "description": "The second line of the address. For example, suite or apartment number.", + "type": "string" + }, + "postalCode": { + "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).", + "type": "string" + }, + "state": { + "description": "The [code](https://about.usps.com/who/profile/history/state-abbreviations.htm) for a US state or the equivalent for other countries.", + "type": "string" + } + }, + "required": [ + "line1", + "city", + "countryCode" + ], + "title": "Simple Postal Address (Coarse-Grained)", + "type": "object" + }, + "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": "#/$defs/currency-code" + }, + "value": { + "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).", + "format": "int64", + "type": "integer" + } + }, + "title": "Simple Money", + "type": "object" + }, + "uuid": { + "description": "A universally unique identifier (UUID) in [RFC-4122 format](https://tools.ietf.org/html/rfc4122).", + "type": "string" + } + }, + "baseUrl": "https://api.ote-godaddy.com", + "description": "The GoDaddy Domain Lifecycle Management API provides comprehensive capabilities\nfor discovering, registering, managing, renewing, transferring, and reselling\ndomain names. This is major version 3, designed for agent-first interactions\nwhile remaining fully usable by direct API clients and resellers.\n\n## Namespace\n\nAll paths are under `/v3/domains/`. The namespace is `domains` (the business capability);\nthe core entity collection is `/domain-names`.\n\n## Key Conventions\n\n**Quote/execute for commercial operations.** Every commercial mutation (register,\nrenew, transfer) requires a `quoteToken` minted by the corresponding quote\ncollection endpoint. Execution without a prior quote is structurally impossible.\nThe token locks the price, resolved settings, and required legal agreements for a\n10-minute TTL. Quote calls are free, read-only, and safe to call speculatively.\n\n**Async commercial operations.** `POST /registrations`, `POST /renewals`, and\n`POST /transfers` each return `202 Accepted` with the concrete entity body\n(`Registration`, `Renewal`, or `Transfer`) and a `Location` header. Poll\n`links[rel=self]` on the returned entity until status is `COMPLETED` or `FAILED`.\nEach concrete resource is also reachable via `GET /operations/{operationId}`;\nthe `operationId` is included in the entity for clients that prefer the abstract\nview. Non-commercial mutations return a `DomainOperation` body.\n\n**Entity-oriented resource model.** Domains are the core entity of this API\n(exposed as `/domain-names` in the path to distinguish the resource collection\nfrom the `domains` namespace prefix). Register, renew, and transfer are\ncommercial actions executed by `POST` to their corresponding top-level\nresource collections (`/registrations`, `/renewals`, `/transfers`); each\naccepts a prior quote and returns an async entity to poll until completion.\nFor commercial execute calls, the target domain is\nexpressed in the request body, not the path. Sub-resources (`contacts`,\n`nameservers`, `privacy`, `auto-renew`, `transfer-lock`, `records`) only exist\nin the context of a specific domain-name instance. The `/check-availability`\ncontroller accepts GET (single domain) or POST (1–50 domains) and carries no\npersistent identity.\n\n**Flat, two-level maximum.** No resource path goes deeper than\n`/{collection}/{id}/{sub-resource}` or `/{collection}/{id}/{sub-collection}/{id}`.\n\n**Reseller on-behalf-of.** Resellers pass `X-Shopper-Id`; all operations are\nthen scoped to that shopper. Absent the header, the authenticated entity's own\naccount is used.\n\n## Launch Scope (v3.0)\nStandard TLDs only. TLDs with eligibility requirements (.us, .ca, .eu) return\n`UNSUPPORTED_TLD` until Phase 2.\n", + "endpoints": [ + { + "method": "GET", + "operationId": "list", + "parameters": [ + { + "description": "Shopper ID whose domains are to be retrieved", + "in": "header", + "name": "X-Shopper-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Only include results with `status` value in the specified set", + "in": "query", + "name": "statuses", + "required": false, + "schema": { + "items": { + "enum": [ + "ACTIVE", + "AWAITING_CLAIM_ACK", + "AWAITING_DOCUMENT_AFTER_TRANSFER", + "AWAITING_DOCUMENT_AFTER_UPDATE_ACCOUNT", + "AWAITING_DOCUMENT_UPLOAD", + "AWAITING_FAILED_TRANSFER_WHOIS_PRIVACY", + "AWAITING_PAYMENT", + "AWAITING_RENEWAL_TRANSFER_IN_COMPLETE", + "AWAITING_TRANSFER_IN_ACK", + "AWAITING_TRANSFER_IN_AUTH", + "AWAITING_TRANSFER_IN_AUTO", + "AWAITING_TRANSFER_IN_WHOIS", + "AWAITING_TRANSFER_IN_WHOIS_FIX", + "AWAITING_VERIFICATION_ICANN", + "AWAITING_VERIFICATION_ICANN_MANUAL", + "CANCELLED", + "CANCELLED_HELD", + "CANCELLED_REDEEMABLE", + "CANCELLED_TRANSFER", + "CONFISCATED", + "DISABLED_SPECIAL", + "EXCLUDED_INVALID_CLAIM_FIREHOSE", + "EXPIRED_REASSIGNED", + "FAILED_BACKORDER_CAPTURE", + "FAILED_DROP_IMMEDIATE_THEN_ADD", + "FAILED_PRE_REGISTRATION", + "FAILED_REDEMPTION", + "FAILED_REDEMPTION_REPORT", + "FAILED_REGISTRATION", + "FAILED_REGISTRATION_FIREHOSE", + "FAILED_RESTORATION_REDEMPTION_MOCK", + "FAILED_SETUP", + "FAILED_TRANSFER_IN", + "FAILED_TRANSFER_IN_BAD_STATUS", + "FAILED_TRANSFER_IN_REGISTRY", + "HELD_COURT_ORDERED", + "HELD_DISPUTED", + "HELD_EXPIRATION_PROTECTION", + "HELD_EXPIRED_REDEMPTION_MOCK", + "HELD_REGISTRAR_ADD", + "HELD_REGISTRAR_REMOVE", + "HELD_SHOPPER", + "HELD_TEMPORARY", + "LOCKED_ABUSE", + "LOCKED_COPYRIGHT", + "LOCKED_REGISTRY", + "LOCKED_SUPER", + "PARKED_AND_HELD", + "PARKED_EXPIRED", + "PARKED_VERIFICATION_ICANN", + "PENDING_ABORT_CANCEL_SETUP", + "PENDING_AGREEMENT_PRE_REGISTRATION", + "PENDING_APPLY_RENEWAL_CREDITS", + "PENDING_BACKORDER_CAPTURE", + "PENDING_BLOCKED_REGISTRY", + "PENDING_CANCEL_REGISTRANT_PROFILE", + "PENDING_COMPLETE_REDEMPTION_WITHOUT_RECEIPT", + "PENDING_COMPLETE_REGISTRANT_PROFILE", + "PENDING_COO", + "PENDING_COO_COMPLETE", + "PENDING_DNS", + "PENDING_DNS_ACTIVE", + "PENDING_DNS_INACTIVE", + "PENDING_DOCUMENT_VALIDATION", + "PENDING_DOCUMENT_VERIFICATION", + "PENDING_DROP_IMMEDIATE", + "PENDING_DROP_IMMEDIATE_THEN_ADD", + "PENDING_EPP_CREATE", + "PENDING_EPP_DELETE", + "PENDING_EPP_UPDATE", + "PENDING_ESCALATION_REGISTRY", + "PENDING_EXPIRATION", + "PENDING_EXPIRATION_RESPONSE", + "PENDING_EXPIRATION_SYNC", + "PENDING_EXPIRED_REASSIGNMENT", + "PENDING_EXPIRE_AUTO_ADD", + "PENDING_EXTEND_REGISTRANT_PROFILE", + "PENDING_FAILED_COO", + "PENDING_FAILED_EPP_CREATE", + "PENDING_FAILED_HELD", + "PENDING_FAILED_PURCHASE_PREMIUM", + "PENDING_FAILED_RECONCILE_FIREHOSE", + "PENDING_FAILED_REDEMPTION_WITHOUT_RECEIPT", + "PENDING_FAILED_RELEASE_PREMIUM", + "PENDING_FAILED_RENEW_EXPIRATION_PROTECTION", + "PENDING_FAILED_RESERVE_PREMIUM", + "PENDING_FAILED_SUBMIT_FIREHOSE", + "PENDING_FAILED_TRANSFER_ACK_PREMIUM", + "PENDING_FAILED_TRANSFER_IN_ACK_PREMIUM", + "PENDING_FAILED_TRANSFER_IN_PREMIUM", + "PENDING_FAILED_TRANSFER_PREMIUM", + "PENDING_FAILED_TRANSFER_SUBMIT_PREMIUM", + "PENDING_FAILED_UNLOCK_PREMIUM", + "PENDING_FAILED_UPDATE_API", + "PENDING_FRAUD_VERIFICATION", + "PENDING_FRAUD_VERIFIED", + "PENDING_GET_CONTACTS", + "PENDING_GET_HOSTS", + "PENDING_GET_NAME_SERVERS", + "PENDING_GET_STATUS", + "PENDING_HOLD_ESCROW", + "PENDING_HOLD_REDEMPTION", + "PENDING_LOCK_CLIENT_REMOVE", + "PENDING_LOCK_DATA_QUALITY", + "PENDING_LOCK_THEN_HOLD_REDEMPTION", + "PENDING_PARKING_DETERMINATION", + "PENDING_PARK_INVALID_WHOIS", + "PENDING_PARK_INVALID_WHOIS_REMOVAL", + "PENDING_PURCHASE_PREMIUM", + "PENDING_RECONCILE", + "PENDING_RECONCILE_FIREHOSE", + "PENDING_REDEMPTION", + "PENDING_REDEMPTION_REPORT", + "PENDING_REDEMPTION_REPORT_COMPLETE", + "PENDING_REDEMPTION_REPORT_SUBMITTED", + "PENDING_REDEMPTION_WITHOUT_RECEIPT", + "PENDING_REDEMPTION_WITHOUT_RECEIPT_MOCK", + "PENDING_RELEASE_PREMIUM", + "PENDING_REMOVAL", + "PENDING_REMOVAL_HELD", + "PENDING_REMOVAL_PARKED", + "PENDING_REMOVAL_UNPARK", + "PENDING_RENEWAL", + "PENDING_RENEW_EXPIRATION_PROTECTION", + "PENDING_RENEW_INFINITE", + "PENDING_RENEW_LOCKED", + "PENDING_RENEW_WITHOUT_RECEIPT", + "PENDING_REPORT_REDEMPTION_WITHOUT_RECEIPT", + "PENDING_RESERVE_PREMIUM", + "PENDING_RESET_VERIFICATION_ICANN", + "PENDING_RESPONSE_FIREHOSE", + "PENDING_RESTORATION", + "PENDING_RESTORATION_INACTIVE", + "PENDING_RESTORATION_REDEMPTION_MOCK", + "PENDING_RETRY_EPP_CREATE", + "PENDING_RETRY_HELD", + "PENDING_SEND_AUTH_CODE", + "PENDING_SETUP", + "PENDING_SETUP_ABANDON", + "PENDING_SETUP_AGREEMENT_LANDRUSH", + "PENDING_SETUP_AGREEMENT_SUNRISE2_A", + "PENDING_SETUP_AGREEMENT_SUNRISE2_B", + "PENDING_SETUP_AGREEMENT_SUNRISE2_C", + "PENDING_SETUP_AUTH", + "PENDING_SETUP_DNS", + "PENDING_SETUP_FAILED", + "PENDING_SETUP_REVIEW", + "PENDING_SETUP_SUNRISE", + "PENDING_SETUP_SUNRISE_PRE", + "PENDING_SETUP_SUNRISE_RESPONSE", + "PENDING_SUBMIT_FAILURE", + "PENDING_SUBMIT_FIREHOSE", + "PENDING_SUBMIT_HOLD_FIREHOSE", + "PENDING_SUBMIT_HOLD_LANDRUSH", + "PENDING_SUBMIT_HOLD_SUNRISE", + "PENDING_SUBMIT_LANDRUSH", + "PENDING_SUBMIT_RESPONSE_FIREHOSE", + "PENDING_SUBMIT_RESPONSE_LANDRUSH", + "PENDING_SUBMIT_RESPONSE_SUNRISE", + "PENDING_SUBMIT_SUCCESS_FIREHOSE", + "PENDING_SUBMIT_SUCCESS_LANDRUSH", + "PENDING_SUBMIT_SUCCESS_SUNRISE", + "PENDING_SUBMIT_SUNRISE", + "PENDING_SUBMIT_WAITING_LANDRUSH", + "PENDING_SUCCESS_PRE_REGISTRATION", + "PENDING_SUSPENDED_DATA_QUALITY", + "PENDING_TRANSFER_ACK_PREMIUM", + "PENDING_TRANSFER_IN", + "PENDING_TRANSFER_IN_ACK", + "PENDING_TRANSFER_IN_ACK_PREMIUM", + "PENDING_TRANSFER_IN_BAD_REGISTRANT", + "PENDING_TRANSFER_IN_CANCEL", + "PENDING_TRANSFER_IN_CANCEL_REGISTRY", + "PENDING_TRANSFER_IN_COMPLETE_ACK", + "PENDING_TRANSFER_IN_DELETE", + "PENDING_TRANSFER_IN_LOCK", + "PENDING_TRANSFER_IN_NACK", + "PENDING_TRANSFER_IN_NOTIFICATION", + "PENDING_TRANSFER_IN_PREMIUM", + "PENDING_TRANSFER_IN_RELEASE", + "PENDING_TRANSFER_IN_RESPONSE", + "PENDING_TRANSFER_IN_UNDERAGE", + "PENDING_TRANSFER_OUT", + "PENDING_TRANSFER_OUT_ACK", + "PENDING_TRANSFER_OUT_NACK", + "PENDING_TRANSFER_OUT_PREMIUM", + "PENDING_TRANSFER_OUT_UNDERAGE", + "PENDING_TRANSFER_OUT_VALIDATION", + "PENDING_TRANSFER_PREMIUM", + "PENDING_TRANSFER_PREMUIM", + "PENDING_TRANSFER_SUBMIT_PREMIUM", + "PENDING_UNLOCK_DATA_QUALITY", + "PENDING_UNLOCK_PREMIUM", + "PENDING_UPDATE", + "PENDING_UPDATED_REGISTRANT_DATA_QUALITY", + "PENDING_UPDATE_ACCOUNT", + "PENDING_UPDATE_API", + "PENDING_UPDATE_API_RESPONSE", + "PENDING_UPDATE_AUTH", + "PENDING_UPDATE_CONTACTS", + "PENDING_UPDATE_CONTACTS_PRIVACY", + "PENDING_UPDATE_DNS", + "PENDING_UPDATE_DNS_SECURITY", + "PENDING_UPDATE_ELIGIBILITY", + "PENDING_UPDATE_EPP_CONTACTS", + "PENDING_UPDATE_MEMBERSHIP", + "PENDING_UPDATE_OWNERSHIP", + "PENDING_UPDATE_OWNERSHIP_AUTH_AUCTION", + "PENDING_UPDATE_OWNERSHIP_HELD", + "PENDING_UPDATE_REGISTRANT", + "PENDING_UPDATE_REPO", + "PENDING_VALIDATION_DATA_QUALITY", + "PENDING_VERIFICATION_FRAUD", + "PENDING_VERIFICATION_STATUS", + "PENDING_VERIFY_REGISTRANT_DATA_QUALITY", + "RESERVED", + "RESERVED_PREMIUM", + "REVERTED", + "SUSPENDED_VERIFICATION_ICANN", + "TRANSFERRED_OUT", + "UNLOCKED_ABUSE", + "UNLOCKED_SUPER", + "UNPARKED_AND_UNHELD", + "UPDATED_OWNERSHIP", + "UPDATED_OWNERSHIP_HELD" + ], + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Only include results with `status` value in any of the specified groups", + "in": "query", + "name": "statusGroups", + "required": false, + "schema": { + "items": { + "enum": [ + "INACTIVE", + "PRE_REGISTRATION", + "REDEMPTION", + "RENEWABLE", + "VERIFICATION_ICANN", + "VISIBLE" + ], + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Maximum number of domains to return", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Marker Domain to use as the offset in results", + "in": "query", + "name": "marker", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Optional details to be included in the response", + "in": "query", + "name": "includes", + "required": false, + "schema": { + "items": { + "enum": [ + "authCode", + "contacts", + "nameServers" + ], + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Only include results that have been modified since the specified date", + "in": "query", + "name": "modifiedDate", + "required": false, + "schema": { + "format": "iso-datetime", + "type": "string" + } + } + ], + "path": "/v1/domains", + "responses": { + "200": { + "description": "Request was successful", + "schema": { + "items": { + "$ref": "#/$defs/V1DomainSummary" + }, + "type": "array" + } + } + }, + "scopes": [], + "summary": "Retrieve a list of Domains for the specified Shopper" + }, + { + "method": "GET", + "operationId": "agreements", + "parameters": [ + { + "description": "Unique identifier of the Market used to retrieve/translate Legal Agreements", + "in": "header", + "name": "X-Market-Id", + "required": false, + "schema": { + "default": "en-US", + "format": "bcp-47", + "type": "string" + } + }, + { + "description": "list of TLDs whose legal agreements are to be retrieved", + "in": "query", + "name": "tlds", + "required": true, + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Whether or not privacy has been requested", + "in": "query", + "name": "privacy", + "required": true, + "schema": { + "type": "boolean" + } + }, + { + "description": "Whether or not domain tranfer has been requested", + "in": "query", + "name": "forTransfer", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "path": "/v1/domains/agreements", + "responses": { + "200": { + "description": "Request was successful", + "schema": { + "items": { + "$ref": "#/$defs/V1LegalAgreement" + }, + "type": "array" + } + } + }, + "scopes": [], + "summary": "Retrieve the legal agreement(s) required to purchase the specified TLD and add-ons" + }, + { + "description": "Returns an indicative availability result for one domain, including\nper-term pricing when available. Availability is best-effort; the\nauthoritative check is performed at quote time. This operation does\nnot persist the check — there is no check identity or poll URL.\n\nEquivalent to POST /check-availability with `domains: [domain]` and the\nsame optional criteria (`optimizeFor`, `iscCode`). The response is that\nsingle `Availability` item unwrapped for convenience — POST returns the\nsame result inside `{ items: [...] }`. Use POST when checking multiple\ndomains in one request.\n\nA domain that cannot be checked is still returned as a `200` with an\n`error` object on the body (the same per-item contract as POST); request-\nlevel failures use the `4xx` responses.\n", + "method": "GET", + "operationId": "getDomainAvailability", + "parameters": [ + { + "description": "Optional client-generated request correlation identifier, propagated across services and returned in the response X-Request-Id header.\n", + "in": "header", + "name": "X-Request-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "X-Shopper-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "The domain name to check, in punycode A-label form for IDNs.", + "in": "query", + "name": "domain", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional. When omitted, defaults to SPEED. Availability is always re-verified authoritatively at quote time regardless of this setting.\n", + "in": "query", + "name": "optimizeFor", + "required": false, + "schema": { + "allOf": [ + { + "$ref": "#/$defs/OptimizationTarget" + } + ], + "default": "SPEED" + } + }, + { + "description": "ISC (International Shopper Code) for pricing context. When provided, prices reflect the applicable rates for this ISC.\n", + "in": "query", + "name": "iscCode", + "required": false, + "schema": { + "type": "string" + } + } + ], + "path": "/v3/domains/check-availability", + "responses": { + "200": { + "description": "Availability result for the requested domain.", + "schema": { + "$ref": "#/$defs/Availability" + } + } + }, + "scopes": [ + "domains.domain:read" + ], + "summary": "Check availability of a single domain" + }, + { + "description": "Batch controller for domain availability checking. Accepts 1–50 domain\nnames alongside optional check criteria (optimization mode, ISC pricing\ncode). Returns one Availability result per requested domain in input\norder inside `{ items: [...] }`. Domains that cannot be checked carry\nan `error` object on that item.\n\nFor a single domain, GET /check-availability (getDomainAvailability)\noffers the same check semantics and Availability result without a\nrequest body; the response is the lone item unwrapped.\n\nAvailability is best-effort indicative; the authoritative check is\nalways performed at quote time. This controller does not persist the\ncheck — there is no check identity or poll URL.\n", + "method": "POST", + "operationId": "checkAvailability", + "parameters": [ + { + "description": "Optional client-generated request correlation identifier, propagated across services and returned in the response X-Request-Id header.\n", + "in": "header", + "name": "X-Request-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "X-Shopper-Id", + "required": false, + "schema": { + "type": "string" + } + } + ], + "path": "/v3/domains/check-availability", + "requestBody": { + "contentType": "application/json", + "required": true, + "schema": { + "$ref": "#/$defs/AvailabilityCheckCriteria" + } + }, + "responses": { + "200": { + "description": "Per-domain availability results in request order. Uncheckable items carry an error object.\n", + "schema": { + "properties": { + "items": { + "description": "Availability results in the same order as the domains array in the request body.\n", + "items": { + "$ref": "#/$defs/Availability" + }, + "type": "array" + } + }, + "required": [ + "items" + ], + "type": "object" + } + } + }, + "scopes": [ + "domains.domain:read" + ], + "summary": "Check availability of one or more specific domains" + }, + { + "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.\n", + "method": "GET", + "operationId": "getDomain", + "parameters": [ + { + "description": "Optional client-generated request correlation identifier, propagated across services and returned in the response X-Request-Id header.\n", + "in": "header", + "name": "X-Request-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "X-Shopper-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "The domain name in punycode A-label form (e.g., example.com). For IDNs, use the punycode representation.\n", + "in": "path", + "name": "domain-name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "path": "/v3/domains/domain-names/{domain-name}", + "responses": { + "200": { + "description": "Domain found.", + "schema": { + "$ref": "#/$defs/Domain" + } + } + }, + "scopes": [ + "domains.domain:read" + ], + "summary": "Get a registered 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.\n", + "method": "PUT", + "operationId": "updateNameservers", + "parameters": [ + { + "description": "Optional client-generated request correlation identifier, propagated across services and returned in the response X-Request-Id header.\n", + "in": "header", + "name": "X-Request-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "X-Shopper-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "The domain name in punycode A-label form (e.g., example.com). For IDNs, use the punycode representation.\n", + "in": "path", + "name": "domain-name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "Idempotency-Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "path": "/v3/domains/domain-names/{domain-name}/nameservers", + "requestBody": { + "contentType": "application/json", + "required": true, + "schema": { + "$ref": "#/$defs/NameServers" + } + }, + "responses": { + "202": { + "description": "Nameserver update accepted; poll the operation for completion.", + "schema": { + "$ref": "#/$defs/DomainOperation" + } + } + }, + "scopes": [ + "domains.nameserver:update" + ], + "summary": "Replace the nameservers for a domain" + }, + { + "description": "Universal poll endpoint for all asynchronous domain mutations. Returns\nthe current state of the operation. Non-terminal responses include a\n`Retry-After` header.\n\nTerminal statuses:\n- `COMPLETED` — operation succeeded; `result` contains the final outcome.\n- `FAILED` — operation terminated with an error; `error` contains detail.\n\nWhile status is non-terminal (`CONFIRMED`, `EXECUTING`), neither\n`result` nor `error` is present. Poll until a terminal status is reached.\n\nThe poll URL is provided in the `Location` header of the initiating 202\nresponse and in `links[rel=self]`. Clients must not construct this URL\nindependently.\n", + "method": "GET", + "operationId": "getOperation", + "parameters": [ + { + "description": "Optional client-generated request correlation identifier, propagated across services and returned in the response X-Request-Id header.\n", + "in": "header", + "name": "X-Request-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "X-Shopper-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "The server-assigned operation identifier returned in the 202 response of any async domain mutation.\n", + "in": "path", + "name": "operationId", + "required": true, + "schema": { + "$ref": "#/$defs/uuid" + } + } + ], + "path": "/v3/domains/operations/{operationId}", + "responses": { + "200": { + "description": "Current operation state.", + "schema": { + "$ref": "#/$defs/DomainOperation" + } + } + }, + "scopes": [ + "domains.domain:read" + ], + "summary": "Poll an async domain operation" + }, + { + "description": "Prices the registration, resolves contact and preference settings,\nreturns required legal agreements, and mints a single-use quoteToken\nwith a 10-minute TTL. Free and read-only; safe to call speculatively.\n\nWhen the domain is unavailable, `available: false` is returned with\nno quoteToken — this is a valid non-error response.\n\nWhen required contact fields are missing, a `422` is returned with\nfield-level details so the agent can collect the missing data and re-quote.\n\nMay pass `iscCode` to lock pricing at applicable rates;\nthe same value must be supplied on /registrations if provided here.\n", + "method": "POST", + "operationId": "quoteDomainRegistration", + "parameters": [ + { + "description": "Optional client-generated request correlation identifier, propagated across services and returned in the response X-Request-Id header.\n", + "in": "header", + "name": "X-Request-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "X-Shopper-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "ISC (International Shopper Code) for pricing context. When provided, prices reflect the applicable rates for this ISC.\n", + "in": "query", + "name": "iscCode", + "required": false, + "schema": { + "type": "string" + } + } + ], + "path": "/v3/domains/registration-quotes", + "requestBody": { + "contentType": "application/json", + "required": true, + "schema": { + "properties": { + "domain": { + "description": "The domain name to quote, in punycode A-label form.", + "example": "example.com", + "type": "string" + }, + "period": { + "default": 1, + "description": "Registration period in years.", + "maximum": 10, + "minimum": 1, + "type": "integer" + }, + "profile": { + "allOf": [ + { + "$ref": "#/$defs/InlineRegistrationProfile" + } + ], + "description": "One-time inline contacts and purchase preference defaults for this quote. Not persisted.\n" + }, + "profileId": { + "allOf": [ + { + "$ref": "#/$defs/uuid" + } + ], + "description": "ID of a saved registration profile. Omit to fall back to the account-default profile or account identity.\n" + } + }, + "required": [ + "domain" + ], + "type": "object" + } + }, + "responses": { + "200": { + "description": "Registration quote. When available is false, no quoteToken is returned; this is not an error.\n", + "schema": { + "$ref": "#/$defs/RegistrationQuote" + } + } + }, + "scopes": [ + "domains.domain:read" + ], + "summary": "Quote a single-domain registration (no commitment)" + }, + { + "description": "Executes a previously quoted domain registration. **Irreversible once\naccepted; creates a charge.** Requires a valid unexpired quoteToken from\n`quoteDomainRegistration`, an `Idempotency-Key` header, and a consent\nrecord. The target domain and period are in the request body alongside\nthe quoteToken.\n\nIdempotency takes precedence over the single-use check: retrying with\nthe same `Idempotency-Key` replays the original operation even after\nthe token is consumed.\n\nReturns a `Registration` entity. Poll `links[rel=self]`\n(`GET /registrations/{registrationId}`) until status is `COMPLETED` or\n`FAILED`. The `operationId` field is also provided for clients that\nprefer `GET /operations/{operationId}`; both resolve the same resource.\n\nPoll either until status is `COMPLETED` or `FAILED`. The operation is\nfire-and-forget; always poll at least once even if the server completed\nit synchronously.\n\nWhen `iscCode` was supplied at quote time, the same value must be\nprovided here or the request fails with `quote_mismatch`.\n", + "method": "POST", + "operationId": "registerDomain", + "parameters": [ + { + "description": "Optional client-generated request correlation identifier, propagated across services and returned in the response X-Request-Id header.\n", + "in": "header", + "name": "X-Request-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "X-Shopper-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "Idempotency-Key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "ISC (International Shopper Code) for pricing context. When provided, prices reflect the applicable rates for this ISC.\n", + "in": "query", + "name": "iscCode", + "required": false, + "schema": { + "type": "string" + } + } + ], + "path": "/v3/domains/registrations", + "requestBody": { + "contentType": "application/json", + "required": true, + "schema": { + "$ref": "#/$defs/Registration" + } + }, + "responses": { + "202": { + "description": "Registration accepted. Poll the self link for status.\n", + "schema": { + "$ref": "#/$defs/Registration" + } + } + }, + "scopes": [ + "domains.domain:create" + ], + "summary": "Register a domain (requires quoteToken)" + }, + { + "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}.\n", + "method": "GET", + "operationId": "getRegistration", + "parameters": [ + { + "description": "Optional client-generated request correlation identifier, propagated across services and returned in the response X-Request-Id header.\n", + "in": "header", + "name": "X-Request-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "X-Shopper-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Server-assigned registration identifier.", + "in": "path", + "name": "registrationId", + "required": true, + "schema": { + "$ref": "#/$defs/uuid" + } + } + ], + "path": "/v3/domains/registrations/{registrationId}", + "responses": { + "200": { + "description": "Registration record returned.", + "schema": { + "$ref": "#/$defs/Registration" + } + } + }, + "scopes": [ + "domains.domain:read" + ], + "summary": "Get a registration record" + }, + { + "description": "Returns available domain name suggestions for a natural-language query\nor keyword set. All results are available (available-only contract).\nPrices are indicative; the authoritative price and availability check\nis at quote time.\n", + "method": "GET", + "operationId": "suggestDomains", + "parameters": [ + { + "description": "Optional client-generated request correlation identifier, propagated across services and returned in the response X-Request-Id header.\n", + "in": "header", + "name": "X-Request-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "X-Shopper-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Natural-language query or keywords describing the desired domain, e.g. \"sunrise bakery\". Used to generate creative and keyword-spin suggestions.\n", + "in": "query", + "name": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Top-level domains to be included in suggestions.", + "in": "query", + "name": "tlds", + "required": false, + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Maximum length of second-level domain.", + "in": "query", + "name": "lengthMax", + "required": false, + "schema": { + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Minimum length of second-level domain.", + "in": "query", + "name": "lengthMin", + "required": false, + "schema": { + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Maximum number of suggestions in the response. Defaults to 10 when omitted.\n", + "in": "query", + "name": "pageSize", + "required": false, + "schema": { + "default": 10, + "maximum": 50, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Suggestion source strategies to activate.\n", + "in": "query", + "name": "sources", + "required": false, + "schema": { + "items": { + "$ref": "#/$defs/SuggestionSource" + }, + "type": "array" + } + } + ], + "path": "/v3/domains/suggestions", + "responses": { + "200": { + "description": "Suggested available domains sorted by relevance.", + "schema": { + "properties": { + "items": { + "description": "Available domain suggestions, sorted by relevance. All items are available by contract.\n", + "items": { + "$ref": "#/$defs/Suggestion" + }, + "type": "array" + } + }, + "required": [ + "items" + ], + "type": "object" + } + } + }, + "scopes": [ + "domains.domain:read" + ], + "summary": "Suggest available domains for a query" + }, + { + "description": "Returns a paginated collection of DNS resource records for the\nspecified zone. Supports filtering by record type and host name,\nfield projection, and page-based pagination.\n\nPagination uses page (1-based) and pageSize query parameters.\nPass totalRequired=true to include totalItems and totalPages when\nat least one record matches; both are omitted for empty result\nsets. Defaults to false to avoid count-query overhead.\n\nFilter parameters are combined with logical AND. Pagination links\nin the response preserve active filter, pagination, and\nfield-projection parameters.\n\nsortBy and sortOrder are not supported. Results are always\nreturned in canonical zone-file order: resource record type (IANA\nRR type number ascending — e.g. A before NS before CNAME), then\nname, then data. This matches authoritative DNS ordering and is\nnot client-configurable.\n", + "method": "GET", + "operationId": "listDNSRecords", + "parameters": [ + { + "description": "Optional client-generated request correlation identifier, propagated across services and returned in the response X-Request-Id header.\n", + "in": "header", + "name": "X-Request-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "X-Shopper-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "The domain name in punycode A-label form (e.g., example.com). For IDNs, use the punycode representation.\n", + "in": "path", + "name": "zone", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "One-based page number for offset-based pagination. Defaults to 1.\n", + "in": "query", + "name": "page", + "required": false, + "schema": { + "default": 1, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Maximum number of items to return per page.\n", + "in": "query", + "name": "pageSize", + "required": false, + "schema": { + "default": 25, + "maximum": 100, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "When true, the response includes totalItems and totalPages for the current filter when at least one record matches. Both are omitted when the result set is empty. Defaults to false; omitting totals avoids the cost of a count query on large collections.\n", + "in": "query", + "name": "totalRequired", + "required": false, + "schema": { + "default": false, + "type": "boolean" + } + }, + { + "description": "Comma-separated list of fields to include in each item of the response. Omitted fields are excluded from the payload. When absent, all fields are returned. Field names must match properties defined on the item schema for the operation; any unknown or invalid name returns 400 Bad Request.\n", + "in": "query", + "name": "fields", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Filter results to records of this DNS type.", + "in": "query", + "name": "type", + "required": false, + "schema": { + "$ref": "#/$defs/DNSRecordType" + } + }, + { + "description": "Filter results to records with this host name relative to the zone. Use `@` for the zone apex.\n", + "in": "query", + "name": "name", + "required": false, + "schema": { + "type": "string" + } + } + ], + "path": "/v3/domains/zones/{zone}/dns-records", + "responses": { + "200": { + "description": "Paginated DNS records for the zone.", + "schema": { + "$ref": "#/$defs/DNSRecords" + } + } + }, + "scopes": [ + "domains.domain:read" + ], + "summary": "List DNS records in a zone" + }, + { + "description": "Creates a new DNS record in the GoDaddy-managed zone. Changes are applied synchronously; no operation polling required.\n", + "method": "POST", + "operationId": "createDNSRecord", + "parameters": [ + { + "description": "Optional client-generated request correlation identifier, propagated across services and returned in the response X-Request-Id header.\n", + "in": "header", + "name": "X-Request-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "X-Shopper-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "The domain name in punycode A-label form (e.g., example.com). For IDNs, use the punycode representation.\n", + "in": "path", + "name": "zone", + "required": true, + "schema": { + "type": "string" + } + } + ], + "path": "/v3/domains/zones/{zone}/dns-records", + "requestBody": { + "contentType": "application/json", + "required": true, + "schema": { + "$ref": "#/$defs/DNSRecord" + } + }, + "responses": { + "201": { + "description": "DNS record created.", + "schema": { + "$ref": "#/$defs/DNSRecord" + } + } + }, + "scopes": [ + "domains.dns:update" + ], + "summary": "Create a DNS record for a zone" + }, + { + "description": "Fully replaces an existing DNS resource record identified by\nrecordId within the zone. All writable fields (name, type, data,\nttl) must be supplied; partial updates are not supported on this\nendpoint. Changes are applied synchronously.\n\nGoDaddy-managed system records (SOA and NS) are read-only. When\nrecordId refers to such a record, the request fails with\n`409 Conflict` — the record exists but cannot be modified.\n", + "method": "PUT", + "operationId": "replaceDNSRecord", + "parameters": [ + { + "description": "Optional client-generated request correlation identifier, propagated across services and returned in the response X-Request-Id header.\n", + "in": "header", + "name": "X-Request-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "X-Shopper-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "The domain name in punycode A-label form (e.g., example.com). For IDNs, use the punycode representation.\n", + "in": "path", + "name": "zone", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Server-assigned DNS record identifier within the zone.", + "in": "path", + "name": "recordId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "path": "/v3/domains/zones/{zone}/dns-records/{recordId}", + "requestBody": { + "contentType": "application/json", + "required": true, + "schema": { + "$ref": "#/$defs/DNSRecord" + } + }, + "responses": { + "200": { + "description": "DNS record replaced.", + "schema": { + "$ref": "#/$defs/DNSRecord" + } + } + }, + "scopes": [ + "domains.dns:update" + ], + "summary": "Replace a DNS record" + }, + { + "description": "Permanently removes a DNS resource record from the zone. The\nrecordId must refer to an existing record within the specified\nzone. Changes are applied synchronously.\n\nGoDaddy-managed system records (SOA and NS) are read-only. When\nrecordId refers to such a record, the request fails with\n`409 Conflict` — the record exists but cannot be deleted.\n", + "method": "DELETE", + "operationId": "deleteDNSRecord", + "parameters": [ + { + "description": "Optional client-generated request correlation identifier, propagated across services and returned in the response X-Request-Id header.\n", + "in": "header", + "name": "X-Request-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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", + "in": "header", + "name": "X-Shopper-Id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "The domain name in punycode A-label form (e.g., example.com). For IDNs, use the punycode representation.\n", + "in": "path", + "name": "zone", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Server-assigned DNS record identifier within the zone.", + "in": "path", + "name": "recordId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "path": "/v3/domains/zones/{zone}/dns-records/{recordId}", + "responses": { + "204": { + "description": "DNS record deleted." + } + }, + "scopes": [ + "domains.dns:update" + ], + "summary": "Delete a DNS record" + } + ], + "name": "domains", + "title": "Domain Lifecycle Management API", + "version": "3.0.0" +} \ No newline at end of file diff --git a/rust/schemas/api/manifest.json b/rust/schemas/api/manifest.json index 575e012..c91c7e0 100644 --- a/rust/schemas/api/manifest.json +++ b/rust/schemas/api/manifest.json @@ -105,6 +105,11 @@ "file": "hosting-nodejs.json", "title": "Node.js Hosting Public API", "endpointCount": 15 + }, + "domains": { + "file": "domains.json", + "title": "Domain Lifecycle Management API", + "endpointCount": 15 } } } \ No newline at end of file diff --git a/rust/src/api_explorer/mod.rs b/rust/src/api_explorer/mod.rs index 50af917..d0910e5 100644 --- a/rust/src/api_explorer/mod.rs +++ b/rust/src/api_explorer/mod.rs @@ -154,6 +154,7 @@ const DOMAIN_FILES: &[(&str, &str)] = &[ "hosting-nodejs", include_str!("../../schemas/api/hosting-nodejs.json"), ), + ("domains", include_str!("../../schemas/api/domains.json")), ]; fn catalog() -> &'static [Domain] { @@ -506,33 +507,6 @@ fn search_command() -> RuntimeCommandSpec { ) } -fn extract_json_path<'a>(value: &'a Value, path: &str) -> Option<&'a Value> { - let normalized = path.trim_start_matches('.'); - if normalized.is_empty() { - return Some(value); - } - let mut current = value; - let mut remaining = normalized; - while !remaining.is_empty() { - if remaining.starts_with('[') { - let end = remaining.find(']')?; - let idx: usize = remaining[1..end].parse().ok()?; - current = current.get(idx)?; - remaining = remaining[end + 1..].trim_start_matches('.'); - } else { - let (key, rest) = match (remaining.find('.'), remaining.find('[')) { - (Some(d), Some(b)) if b < d => (&remaining[..b], &remaining[b..]), - (Some(d), _) => (&remaining[..d], &remaining[d + 1..]), - (None, Some(b)) => (&remaining[..b], &remaining[b..]), - (None, None) => (remaining, ""), - }; - current = current.get(key)?; - remaining = rest; - } - } - Some(current) -} - fn call_command() -> RuntimeCommandSpec { RuntimeCommandSpec::new_with_context( CommandSpec::new("call", "Make an authenticated API request") @@ -544,10 +518,10 @@ fn call_command() -> RuntimeCommandSpec { before the request is sent. Supply the request body as raw JSON (`--body \ '{...}'`), as individual fields (`--field key=value`, repeatable), or \ from a JSON file (`--file body.json`); `--file` takes precedence over \ - `--body`, and `--field` values are merged on top of either. Use `--query \ - .path[0].field` to extract a value from the response JSON, and `--include` \ - to see response headers alongside the body. Use `api describe ` \ - first to inspect required parameters and scopes.", + `--body`, and `--field` values are merged on top of either. Use the global \ + `--expr`/`--filter` flags (JMESPath) to extract or filter response data, and \ + `--include` to see response headers alongside the body. Use \ + `api describe ` first to inspect required parameters and scopes.", ) .with_system("api") .with_tier(Tier::Mutate) @@ -595,13 +569,6 @@ fn call_command() -> RuntimeCommandSpec { .num_args(0..) .help("Extra request headers"), ) - .with_arg( - clap::Arg::new("query") - .long("query") - .short('q') - .value_name("PATH") - .help("Extract a value from the response JSON (e.g. .data[0].id)"), - ) .with_arg( clap::Arg::new("include") .long("include") @@ -790,22 +757,13 @@ fn call_command() -> RuntimeCommandSpec { ))); } - let query_path = ctx.args.get("query").and_then(|v| v.as_str()); - let output = if let Some(path) = query_path { - extract_json_path(&body, path) - .cloned() - .unwrap_or(json!(null)) - } else { - body - }; - // Identify the call and its outcome in the result envelope. let mut result = json!({ "endpoint": endpoint, "method": method, "status": status, "status_text": status_text, - "data": output, + "data": body, }); if let Some(headers) = response_headers { result["headers"] = Value::Object(headers); From 5556a660a474c6055434a1d667fb1adf04aa476a Mon Sep 17 00:00:00 2001 From: Jacob Page Date: Fri, 17 Jul 2026 10:05:53 -0700 Subject: [PATCH 2/4] test(generate-api-catalog): update source-manifest test for the new domains local source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the previous commit's domains catalog source — the test asserting the reconciled domain count/list was still hardcoded to the pre-change values. Co-Authored-By: Claude Sonnet 5 --- rust/tools/generate-api-catalog/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/tools/generate-api-catalog/src/main.rs b/rust/tools/generate-api-catalog/src/main.rs index 47e0081..3df99de 100644 --- a/rust/tools/generate-api-catalog/src/main.rs +++ b/rust/tools/generate-api-catalog/src/main.rs @@ -2260,7 +2260,7 @@ components: let manifest = load_source_manifest().expect("load source manifest"); let expected = manifest.expected_domains(); - assert_eq!(expected.len(), 21); + assert_eq!(expected.len(), 22); assert_eq!(manifest.remote.len(), 20); assert_eq!( manifest @@ -2268,11 +2268,11 @@ components: .iter() .map(|source| source.domain.as_str()) .collect::>(), - ["hosting-nodejs"] + ["hosting-nodejs", "domains"] ); assert_eq!( manifest.legacy_typescript.rust_only_domains, - ["hosting-nodejs"] + ["hosting-nodejs", "domains"] ); } From 31c1b0e979ae387f15fb6a46bab7f1e6fe48c674 Mon Sep 17 00:00:00 2001 From: Jacob Page Date: Fri, 17 Jul 2026 10:06:54 -0700 Subject: [PATCH 3/4] refactor(domains-client): invert cli-engine dependency from pull to push MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit domains-client called straight into cli_engine::transport from inside its progenitor ClientHooks impl, giving a generated, otherwise framework-agnostic client a hard compile-time dependency on cli-engine purely for --debug transport logging — and leaving its own minimum cli-engine version (0.4.0) to drift from the main crate's (0.4.2). Replace it with a TransportObserver trait domains-client defines and owns; the main crate now pushes an adapter into it via set_transport_observer instead of domains-client pulling from the engine. domains-client no longer depends on cli-engine at all. Co-Authored-By: Claude Sonnet 5 --- rust/Cargo.lock | 1 - rust/domains-client/Cargo.toml | 9 +-- rust/domains-client/src/lib.rs | 135 +++++++++++++++++++++------------ rust/src/domain/common.rs | 28 +++++++ 4 files changed, 116 insertions(+), 57 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index ac038f2..1ab6165 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -957,7 +957,6 @@ name = "domains-client" version = "0.1.8" dependencies = [ "bytes", - "cli-engine", "futures", "httpmock", "openapiv3", diff --git a/rust/domains-client/Cargo.toml b/rust/domains-client/Cargo.toml index 7b572a5..fe7b464 100644 --- a/rust/domains-client/Cargo.toml +++ b/rust/domains-client/Cargo.toml @@ -7,12 +7,9 @@ description = "Generated GoDaddy Domains API client (domains list + availability [dependencies] # Pinned to 0.11 (the latest progenitor on reqwest 0.12): keeps the whole -# workspace on one reqwest + ring-based rustls-tls stack as cli-engine/the main -# crate, which avoids aws-lc-rs (reqwest 0.13's only rustls provider) and keeps -# the Windows-msvc cross-build clean. -# Only for the `--debug transport` bridge (see `ClientHooks` impl below) — no -# feature flags needed, `transport` is part of the default lib. -cli-engine = "0.4.0" +# workspace on one reqwest + ring-based rustls-tls stack as the main crate, +# which avoids aws-lc-rs (reqwest 0.13's only rustls provider) and keeps the +# Windows-msvc cross-build clean. progenitor-client = "0.11" reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "stream"] } # Pinned to match cli-engine's schemars so the generated types' derived diff --git a/rust/domains-client/src/lib.rs b/rust/domains-client/src/lib.rs index 15ca0e8..23569ab 100644 --- a/rust/domains-client/src/lib.rs +++ b/rust/domains-client/src/lib.rs @@ -42,8 +42,30 @@ pub enum BuildError { Http(#[from] reqwest::Error), } -/// Bridges generated requests/responses into cli-engine's `--debug transport` -/// logger. +/// Observes every generated request/response, independent of any particular +/// logging backend. This crate has no compile-time dependency on cli-engine +/// (or any other framework) — the *caller* pushes an implementation in via +/// [`set_transport_observer`] rather than this crate pulling one in. +pub trait TransportObserver: Send + Sync { + fn on_request(&self, request: &reqwest::Request); + fn on_response(&self, status: reqwest::StatusCode, headers: &reqwest::header::HeaderMap); +} + +static TRANSPORT_OBSERVER: std::sync::RwLock>> = + std::sync::RwLock::new(None); + +/// Registers (or clears, with `None`) the process-wide transport observer. +/// The main crate calls this with an adapter around its own logging +/// framework — e.g. cli-engine's `--debug transport` bridge — before making +/// any request through a [`Client`]. +pub fn set_transport_observer(observer: Option>) { + *TRANSPORT_OBSERVER + .write() + .expect("lock is never held across a panic") = observer; +} + +/// Bridges generated requests/responses into the registered +/// [`TransportObserver`], if any. /// /// progenitor generates every call as `client.pre(...)`, `client.exec(...)`, /// `client.post(...)` (see [`progenitor_client::ClientHooks`]); the default @@ -53,15 +75,21 @@ pub enum BuildError { /// /// `post` only gets `&reqwest::Result` (a reference, pre-body-read), /// so response bodies aren't capturable here without consuming the body ahead -/// of the generated code's own deserialization — this logs status/headers -/// only for responses; request bodies log in full via `pre`. +/// of the generated code's own deserialization — this reports status/headers +/// only for responses; request bodies are reported in full via `pre`. impl progenitor_client::ClientHooks<()> for Client { async fn pre( &self, request: &mut reqwest::Request, _info: &progenitor_client::OperationInfo, ) -> Result<(), progenitor_client::Error> { - cli_engine::transport::debug_log_reqwest_request(request); + if let Some(observer) = TRANSPORT_OBSERVER + .read() + .expect("lock is never held across a panic") + .as_ref() + { + observer.on_request(request); + } Ok(()) } @@ -70,12 +98,13 @@ impl progenitor_client::ClientHooks<()> for Client { result: &reqwest::Result, _info: &progenitor_client::OperationInfo, ) -> Result<(), progenitor_client::Error> { - if let Ok(response) = result { - cli_engine::transport::debug_log_reqwest_response( - response.status(), - response.headers(), - &[], - ); + if let Ok(response) = result + && let Some(observer) = TRANSPORT_OBSERVER + .read() + .expect("lock is never held across a panic") + .as_ref() + { + observer.on_response(response.status(), response.headers()); } Ok(()) } @@ -646,55 +675,61 @@ mod tests { mock.assert_async().await; } - // --- ClientHooks / --debug transport bridge ------------------------------ + // --- ClientHooks / TransportObserver bridge ------------------------------ #[derive(Debug, Default)] - struct RecordingLogger { - events: std::sync::Mutex>, + struct RecordingObserver { + requests: std::sync::Mutex>, + responses: std::sync::Mutex>, } - impl cli_engine::transport::TransportLogger for RecordingLogger { - fn debug(&self, event: &cli_engine::transport::TransportLogEvent) { - self.events + impl TransportObserver for RecordingObserver { + fn on_request(&self, request: &reqwest::Request) { + self.requests .lock() .expect("lock is never held across a panic") - .push(event.clone()); + .push(request.method().to_string()); + } + + fn on_response(&self, status: reqwest::StatusCode, _headers: &reqwest::header::HeaderMap) { + self.responses + .lock() + .expect("lock is never held across a panic") + .push(status.as_u16()); } } - // Serializes tests that mutate the process-wide default transport logger. - // An async-aware lock, not a `std::sync::Mutex` — the guard is held across + // Serializes tests that mutate the process-wide transport observer. An + // async-aware lock, not a `std::sync::Mutex` — the guard is held across // this test's `.await` points (clippy::await_holding_lock), which is only // sound with a lock that yields the executor instead of blocking a thread. - static TRANSPORT_LOGGER_TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(()); + static TRANSPORT_OBSERVER_TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(()); - // Restores the noop logger on drop so a panicking assertion below can't - // leak a test's logger into later tests in this binary. Declared after - // acquiring `TRANSPORT_LOGGER_TEST_LOCK` so the reset runs while the lock - // is still held. - struct RestoreDefaultTransportLogger; + // Clears the observer on drop so a panicking assertion below can't leak + // a test's observer into later tests in this binary. Declared after + // acquiring `TRANSPORT_OBSERVER_TEST_LOCK` so the reset runs while the + // lock is still held. + struct ClearTransportObserver; - impl Drop for RestoreDefaultTransportLogger { + impl Drop for ClearTransportObserver { fn drop(&mut self) { - cli_engine::transport::set_default_transport_logger(std::sync::Arc::new( - cli_engine::transport::NoopTransportLogger, - )); + set_transport_observer(None); } } // Generated calls thread every request/response through `ClientHooks`, - // which this crate implements (see above) to call cli-engine's - // `debug_log_reqwest_request`/`debug_log_reqwest_response` bridge — the - // mechanism that makes `--debug transport` show HTTP traffic for a - // progenitor-generated client. Assert the bridge actually fires, not just - // that the request/response round-trips. + // which this crate implements (see above) to forward to whatever + // `TransportObserver` the caller has registered — the extension point + // that lets the main crate bridge to its own `--debug transport` logging + // without this crate depending on that framework. Assert the bridge + // actually fires, not just that the request/response round-trips. #[tokio::test] - async fn client_hooks_feed_the_debug_transport_bridge() { - let _test_lock = TRANSPORT_LOGGER_TEST_LOCK.lock().await; - let _restore = RestoreDefaultTransportLogger; + async fn client_hooks_feed_the_registered_observer() { + let _test_lock = TRANSPORT_OBSERVER_TEST_LOCK.lock().await; + let _clear = ClearTransportObserver; - let logger = std::sync::Arc::new(RecordingLogger::default()); - cli_engine::transport::set_default_transport_logger(logger.clone()); + let observer = std::sync::Arc::new(RecordingObserver::default()); + set_transport_observer(Some(observer.clone())); let server = MockServer::start_async().await; let mock = server @@ -712,21 +747,21 @@ mod tests { .expect("request succeeds"); mock.assert_async().await; - let events = logger - .events + let requests = observer + .requests .lock() .expect("lock is never held across a panic"); assert!( - events - .iter() - .any(|e| e.fields.get("method").map(String::as_str) == Some("GET")), - "expected a request event, got: {events:?}" + requests.iter().any(|m| m == "GET"), + "expected a request event, got: {requests:?}" ); + let responses = observer + .responses + .lock() + .expect("lock is never held across a panic"); assert!( - events - .iter() - .any(|e| e.fields.get("status").map(String::as_str) == Some("200")), - "expected a response event, got: {events:?}" + responses.contains(&200), + "expected a response event, got: {responses:?}" ); } } diff --git a/rust/src/domain/common.rs b/rust/src/domain/common.rs index 54868dd..ec34f9a 100644 --- a/rust/src/domain/common.rs +++ b/rust/src/domain/common.rs @@ -14,6 +14,33 @@ fn map_env_err(e: environments::EnvError) -> CliCoreError { CliCoreError::message(e.to_string()) } +/// Bridges domains-client's request/response observations into cli-engine's +/// `--debug transport` logger. domains-client defines the `TransportObserver` +/// extension point itself and has no compile-time dependency on cli-engine — +/// this crate *pushes* the logging behavior in, rather than domains-client +/// *pulling* it from the engine. +struct CliEngineTransportObserver; + +impl domains_client::TransportObserver for CliEngineTransportObserver { + fn on_request(&self, request: &reqwest::Request) { + cli_engine::transport::debug_log_reqwest_request(request); + } + + fn on_response(&self, status: reqwest::StatusCode, headers: &reqwest::header::HeaderMap) { + cli_engine::transport::debug_log_reqwest_response(status, headers, &[]); + } +} + +static TRANSPORT_OBSERVER_INIT: std::sync::Once = std::sync::Once::new(); + +fn ensure_transport_observer_registered() { + TRANSPORT_OBSERVER_INIT.call_once(|| { + domains_client::set_transport_observer(Some(std::sync::Arc::new( + CliEngineTransportObserver, + ))); + }); +} + /// The ISO-4217 minor-unit exponent for a currency — how many implied decimal /// places a [`types::SimpleMoney`] `value` carries (the v3 spec defers money /// formatting to ISO 4217). Sourced from the `iso_currency` crate's maintained @@ -116,6 +143,7 @@ pub(crate) fn make_client_with_cred( env: &str, cred: &Credential, ) -> Result { + ensure_transport_observer_registered(); let domains = environments::resolve_domains(env).map_err(map_env_err)?; let authorization = format!("Bearer {}", cred.token); let request_id = uuid::Uuid::new_v4().to_string(); From a3404828f49ca997b072a3f4dc1796f6ca9eaf10 Mon Sep 17 00:00:00 2001 From: Jacob Page Date: Fri, 17 Jul 2026 12:32:04 -0700 Subject: [PATCH 4/4] fix(domains-client): drop the transport-observer lock before calling out ClientHooks::pre/post called the registered TransportObserver while still holding TRANSPORT_OBSERVER's read guard. Calling out to caller-provided code under a lock risks deadlock if an observer ever needs the write lock (e.g. via set_transport_observer) or re-enters through another hook. Clone the Arc out and drop the guard first. Co-Authored-By: Claude Sonnet 5 --- rust/domains-client/src/lib.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rust/domains-client/src/lib.rs b/rust/domains-client/src/lib.rs index 23569ab..fed0109 100644 --- a/rust/domains-client/src/lib.rs +++ b/rust/domains-client/src/lib.rs @@ -83,11 +83,11 @@ impl progenitor_client::ClientHooks<()> for Client { request: &mut reqwest::Request, _info: &progenitor_client::OperationInfo, ) -> Result<(), progenitor_client::Error> { - if let Some(observer) = TRANSPORT_OBSERVER + let observer = TRANSPORT_OBSERVER .read() .expect("lock is never held across a panic") - .as_ref() - { + .clone(); + if let Some(observer) = observer { observer.on_request(request); } Ok(()) @@ -98,11 +98,12 @@ impl progenitor_client::ClientHooks<()> for Client { result: &reqwest::Result, _info: &progenitor_client::OperationInfo, ) -> Result<(), progenitor_client::Error> { + let observer = TRANSPORT_OBSERVER + .read() + .expect("lock is never held across a panic") + .clone(); if let Ok(response) = result - && let Some(observer) = TRANSPORT_OBSERVER - .read() - .expect("lock is never held across a panic") - .as_ref() + && let Some(observer) = observer { observer.on_response(response.status(), response.headers()); }