{
  "openapi": "3.1.0",
  "info": {
    "title": "Resolvify API",
    "version": "1.0.0",
    "description": "Subdomain discovery and DNS resolution without an API key. All clients share a daily allowance of 1,000 requests per IP identity, resetting at midnight UTC, and a 30-request-per-minute burst allowance. IPv6 clients are grouped by /64 network. Search results are deduplicated and sorted by latest certificate observation descending, then hostname. Observed hostnames may not currently resolve. Cache hits and all endpoints/formats share the same allowance. Service-wide protection limits may also apply."
  },
  "servers": [
    {
      "url": "https://resolvify.xyz",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Interactive documentation and API playground",
    "url": "https://resolvify.xyz/docs"
  },
  "tags": [
    {
      "name": "Discovery",
      "description": "Find observed hostnames under an apex domain."
    },
    {
      "name": "DNS",
      "description": "Resolve current DNS records."
    }
  ],
  "security": [],
  "paths": {
    "/api/v1/search": {
      "get": {
        "operationId": "searchSubdomains",
        "summary": "Discover unique hostnames for an apex domain",
        "description": "Results are normalized, deduplicated by hostname, and sorted newest observation first. Wildcard prefixes are grouped under their base hostname, with wildcard=true. Pagination is applied to the sorted unique dataset. Fresh datasets are cached for 24 hours; empty datasets for 10 minutes. On a failed refresh, a cached dataset up to seven days old may be returned with stale=true. A stale result may also be returned while another request refreshes the dataset. Datasets are bounded; a result that cannot fit within service limits returns an error rather than silently truncating.",
        "tags": ["Discovery"],
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "description": "Registrable apex domain, such as example.com or example.co.uk. Unicode international domain names are accepted and normalized to ASCII.",
            "schema": {
              "type": "string",
              "maxLength": 253
            },
            "example": "example.com"
          },
          {
            "name": "format",
            "in": "query",
            "description": "Response serialization.",
            "schema": {
              "type": "string",
              "enum": ["json", "txt", "xml"],
              "default": "json"
            }
          },
          {
            "name": "view",
            "in": "query",
            "description": "Detailed records or hostname strings only.",
            "schema": {
              "type": "string",
              "enum": ["detailed", "simple"],
              "default": "detailed"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "One-based page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1,
              "maximum": 1000000
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Unique hostnames per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of unique hostnames. An empty dataset is a successful result.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Shared daily allowance per client identity.",
                "schema": {
                  "type": "integer",
                  "example": 1000
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining shared daily allowance.",
                "schema": {
                  "type": "integer",
                  "example": 999
                }
              },
              "X-RateLimit-Reset": {
                "description": "UTC daily reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "example": 1793145600
                }
              },
              "X-Total-Count": {
                "description": "Total unique names before pagination.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Total-Pages": {
                "description": "Total pages for the requested limit.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Page": {
                "description": "Requested page number.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Page-Limit": {
                "description": "Requested results per page.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Request-ID": {
                "description": "Opaque request identifier; also included as requestId in error bodies.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/DetailedSearchResponse"
                    },
                    {
                      "$ref": "#/components/schemas/SimpleSearchResponse"
                    }
                  ]
                },
                "examples": {
                  "detailed": {
                    "summary": "Detailed record; illustrative example",
                    "value": {
                      "domain": "example.com",
                      "total": 1,
                      "page": 1,
                      "limit": 100,
                      "totalPages": 1,
                      "queriedAt": "2026-10-27T00:00:00.000Z",
                      "cached": true,
                      "stale": false,
                      "results": [
                        {
                          "hostname": "www.example.com",
                          "firstSeen": "2025-01-08T10:00:00.000Z",
                          "lastSeen": "2026-10-26T09:42:00.000Z",
                          "certificateCount": 12,
                          "wildcard": false,
                          "issuer": "Example Certificate Authority",
                          "expiresAt": "2027-01-24T09:42:00.000Z"
                        }
                      ]
                    }
                  },
                  "simple": {
                    "summary": "Simple names; illustrative example",
                    "value": {
                      "domain": "example.com",
                      "total": 2,
                      "page": 1,
                      "limit": 100,
                      "totalPages": 1,
                      "queriedAt": "2026-10-27T00:00:00.000Z",
                      "cached": true,
                      "stale": false,
                      "results": ["www.example.com", "api.example.com"]
                    }
                  }
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "examples": {
                  "simple": {
                    "summary": "One hostname per line (view=simple)",
                    "value": "www.example.com\napi.example.com\n"
                  },
                  "detailed": {
                    "summary": "Tab-separated records (view=detailed)",
                    "value": "hostname\tfirst_seen\tlast_seen\tcertificate_count\twildcard\tissuer\texpires_at\nwww.example.com\t2025-01-08T10:00:00.000Z\t2026-10-26T09:42:00.000Z\t12\tfalse\tExample Certificate Authority\t2027-01-24T09:42:00.000Z\n"
                  }
                }
              },
              "application/xml": {
                "schema": {
                  "type": "string"
                },
                "example": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><search domain=\"example.com\" total=\"1\" page=\"1\" limit=\"100\" totalPages=\"1\" queriedAt=\"2026-10-27T00:00:00.000Z\" cached=\"true\" stale=\"false\"><results><record><hostname>www.example.com</hostname><firstSeen>2025-01-08T10:00:00.000Z</firstSeen><lastSeen>2026-10-26T09:42:00.000Z</lastSeen><certificateCount>12</certificateCount><wildcard>false</wildcard></record></results></search>"
              }
            }
          },
          "400": {
            "description": "Invalid domain, query parameter, pagination, or duplicate/unknown parameter.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Shared daily allowance per client identity.",
                "schema": {
                  "type": "integer",
                  "example": 1000
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining shared daily allowance.",
                "schema": {
                  "type": "integer",
                  "example": 999
                }
              },
              "X-RateLimit-Reset": {
                "description": "UTC daily reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "example": 1793145600
                }
              },
              "X-Request-ID": {
                "description": "Opaque request identifier; also included as requestId in error bodies.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The dataset exceeds the bounded response size. The domain is too large for a single search.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "description": "Opaque request identifier; also included as requestId in error bodies.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            }
          },
          "429": {
            "description": "Daily, burst, or service protection limit reached. Wait before retrying.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Shared daily allowance per client identity.",
                "schema": {
                  "type": "integer",
                  "example": 1000
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining shared daily allowance.",
                "schema": {
                  "type": "integer",
                  "example": 999
                }
              },
              "X-RateLimit-Reset": {
                "description": "UTC daily reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "example": 1793145600
                }
              },
              "Retry-After": {
                "description": "Number of seconds before retry.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Request-ID": {
                "description": "Opaque request identifier; also included as requestId in error bodies.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Search is in progress, shared service capacity is reached, or a lookup is unavailable or timed out. Honor Retry-After when present.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Shared daily allowance per client identity.",
                "schema": {
                  "type": "integer",
                  "example": 1000
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining shared daily allowance.",
                "schema": {
                  "type": "integer",
                  "example": 999
                }
              },
              "X-RateLimit-Reset": {
                "description": "UTC daily reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "example": 1793145600
                }
              },
              "Retry-After": {
                "description": "Delay before retry, when available.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Request-ID": {
                "description": "Opaque request identifier; also included as requestId in error bodies.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/resolve": {
      "get": {
        "operationId": "resolveDns",
        "summary": "Resolve DNS records for a domain or subdomain",
        "description": "Returns DNS answers for the selected record type. An empty records array means that no answers were returned for the query. Fresh cache lifetime follows the minimum returned TTL, bounded to 30–300 seconds. On refresh failure, a cached answer up to one hour old may be used. Answers may include CNAME links. A stale result may also be returned while another request refreshes the answer.",
        "tags": ["DNS"],
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "description": "A domain or subdomain. Unicode names are accepted and normalized.",
            "schema": {
              "type": "string",
              "maxLength": 253
            },
            "example": "example.com"
          },
          {
            "name": "type",
            "in": "query",
            "description": "DNS record type.",
            "schema": {
              "type": "string",
              "enum": ["A", "AAAA", "MX", "NS", "TXT", "CNAME", "SOA", "CAA"],
              "default": "A"
            }
          },
          {
            "name": "format",
            "in": "query",
            "description": "Response serialization.",
            "schema": {
              "type": "string",
              "enum": ["json", "txt", "xml"],
              "default": "json"
            }
          },
          {
            "name": "view",
            "in": "query",
            "description": "Detailed record objects or record values only.",
            "schema": {
              "type": "string",
              "enum": ["detailed", "simple"],
              "default": "detailed"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "DNS answers. The array can be empty.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Shared daily allowance per client identity.",
                "schema": {
                  "type": "integer",
                  "example": 1000
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining shared daily allowance.",
                "schema": {
                  "type": "integer",
                  "example": 999
                }
              },
              "X-RateLimit-Reset": {
                "description": "UTC daily reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "example": 1793145600
                }
              },
              "X-Request-ID": {
                "description": "Opaque request identifier; also included as requestId in error bodies.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/DnsResponse"
                    },
                    {
                      "$ref": "#/components/schemas/SimpleDnsResponse"
                    }
                  ]
                },
                "example": {
                  "domain": "example.com",
                  "type": "A",
                  "records": [
                    {
                      "name": "example.com",
                      "type": "A",
                      "ttl": 300,
                      "value": "93.184.215.14"
                    }
                  ],
                  "queriedAt": "2026-10-27T00:00:00.000Z",
                  "cached": false,
                  "stale": false,
                  "status": "NOERROR"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid domain, query parameter, pagination, or duplicate/unknown parameter.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Shared daily allowance per client identity.",
                "schema": {
                  "type": "integer",
                  "example": 1000
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining shared daily allowance.",
                "schema": {
                  "type": "integer",
                  "example": 999
                }
              },
              "X-RateLimit-Reset": {
                "description": "UTC daily reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "example": 1793145600
                }
              },
              "X-Request-ID": {
                "description": "Opaque request identifier; also included as requestId in error bodies.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The dataset exceeds the bounded response size. The domain is too large for a single search.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "description": "Opaque request identifier; also included as requestId in error bodies.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            }
          },
          "429": {
            "description": "Daily, burst, or service protection limit reached. Wait before retrying.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Shared daily allowance per client identity.",
                "schema": {
                  "type": "integer",
                  "example": 1000
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining shared daily allowance.",
                "schema": {
                  "type": "integer",
                  "example": 999
                }
              },
              "X-RateLimit-Reset": {
                "description": "UTC daily reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "example": 1793145600
                }
              },
              "Retry-After": {
                "description": "Number of seconds before retry.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Request-ID": {
                "description": "Opaque request identifier; also included as requestId in error bodies.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Search is in progress, shared service capacity is reached, or a lookup is unavailable or timed out. Honor Retry-After when present.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Shared daily allowance per client identity.",
                "schema": {
                  "type": "integer",
                  "example": 1000
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining shared daily allowance.",
                "schema": {
                  "type": "integer",
                  "example": 999
                }
              },
              "X-RateLimit-Reset": {
                "description": "UTC daily reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "example": 1793145600
                }
              },
              "Retry-After": {
                "description": "Delay before retry, when available.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Request-ID": {
                "description": "Opaque request identifier; also included as requestId in error bodies.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error", "requestId"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable error code.",
                "example": "INVALID_DOMAIN",
                "enum": [
                  "INVALID_DOMAIN",
                  "INVALID_PARAMETER",
                  "RESULT_TOO_LARGE",
                  "RATE_LIMITED",
                  "TEMPORARILY_UNAVAILABLE",
                  "SEARCH_IN_PROGRESS",
                  "SEARCH_BUSY",
                  "SEARCH_CAPACITY_REACHED",
                  "RESULT_UNAVAILABLE"
                ]
              },
              "message": {
                "type": "string",
                "description": "Safe human-readable error message.",
                "example": "Enter an apex domain, such as example.com, without a URL or path."
              }
            }
          },
          "requestId": {
            "type": "string",
            "description": "Opaque request identifier for troubleshooting.",
            "format": "uuid"
          }
        }
      },
      "SubdomainRecord": {
        "type": "object",
        "required": [
          "hostname",
          "firstSeen",
          "lastSeen",
          "certificateCount",
          "wildcard"
        ],
        "properties": {
          "hostname": {
            "type": "string",
            "example": "www.example.com"
          },
          "firstSeen": {
            "type": "string",
            "format": "date-time",
            "description": "Earliest observation in the returned dataset."
          },
          "lastSeen": {
            "type": "string",
            "format": "date-time",
            "description": "Latest observation in the returned dataset; used for descending result order."
          },
          "certificateCount": {
            "type": "integer",
            "minimum": 1,
            "description": "Distinct certificates observed for this hostname."
          },
          "wildcard": {
            "type": "boolean",
            "description": "Whether any observations included a wildcard for this hostname."
          },
          "issuer": {
            "type": "string",
            "description": "Optional issuer from the most recent certificate observation."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "Optional expiry from the most recent certificate observation."
          }
        }
      },
      "SearchMetadata": {
        "type": "object",
        "required": [
          "domain",
          "total",
          "page",
          "limit",
          "totalPages",
          "queriedAt",
          "cached",
          "stale"
        ],
        "properties": {
          "domain": {
            "type": "string"
          },
          "total": {
            "type": "integer",
            "minimum": 0
          },
          "page": {
            "type": "integer",
            "minimum": 1
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          },
          "totalPages": {
            "type": "integer",
            "minimum": 0
          },
          "queriedAt": {
            "type": "string",
            "format": "date-time"
          },
          "cached": {
            "type": "boolean"
          },
          "stale": {
            "type": "boolean"
          }
        }
      },
      "DetailedSearchResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SearchMetadata"
          },
          {
            "type": "object",
            "required": ["results"],
            "properties": {
              "results": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/SubdomainRecord"
                }
              }
            }
          }
        ]
      },
      "SimpleSearchResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SearchMetadata"
          },
          {
            "type": "object",
            "required": ["results"],
            "properties": {
              "results": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        ]
      },
      "DnsRecord": {
        "type": "object",
        "required": ["name", "type", "ttl", "value"],
        "properties": {
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "description": "Record type in the returned answer."
          },
          "ttl": {
            "type": "integer",
            "minimum": 0,
            "description": "DNS answer TTL in seconds."
          },
          "value": {
            "type": "string",
            "description": "DNS answer value."
          }
        }
      },
      "DnsResponse": {
        "type": "object",
        "required": [
          "domain",
          "type",
          "records",
          "queriedAt",
          "cached",
          "stale",
          "status"
        ],
        "properties": {
          "domain": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "records": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DnsRecord"
            }
          },
          "queriedAt": {
            "type": "string",
            "format": "date-time"
          },
          "cached": {
            "type": "boolean"
          },
          "stale": {
            "type": "boolean",
            "description": "Whether an older cached answer was served while a refresh is in progress or unavailable."
          },
          "status": {
            "type": "string",
            "enum": ["NOERROR", "NXDOMAIN"],
            "description": "DNS response status."
          }
        }
      },
      "SimpleDnsResponse": {
        "allOf": [
          {
            "type": "object",
            "required": [
              "domain",
              "type",
              "records",
              "queriedAt",
              "cached",
              "stale",
              "status"
            ],
            "properties": {
              "domain": {
                "type": "string"
              },
              "type": {
                "type": "string"
              },
              "records": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "queriedAt": {
                "type": "string",
                "format": "date-time"
              },
              "cached": {
                "type": "boolean"
              },
              "stale": {
                "type": "boolean",
                "description": "Whether an older cached answer was served while a refresh is in progress or unavailable."
              },
              "status": {
                "type": "string",
                "enum": ["NOERROR", "NXDOMAIN"],
                "description": "DNS response status."
              }
            }
          }
        ]
      }
    }
  }
}
