{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/cheapest-domains/json-schema/cheapest-domains-price-history-schema.json",
  "title": "PriceHistory",
  "x-generated": "2026-11-04",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/cheapest-domains-openapi.json#/components/schemas/PriceHistory",
  "allOf": [
    {
      "$ref": "#/$defs/Envelope"
    },
    {
      "type": "object",
      "required": [
        "comparison",
        "tld",
        "price",
        "pageUrl",
        "historyStart",
        "registrars",
        "truncated",
        "notes"
      ],
      "properties": {
        "comparison": {
          "$ref": "#/$defs/Comparison"
        },
        "tld": {
          "type": "string"
        },
        "price": {
          "enum": [
            "renewal",
            "registration"
          ]
        },
        "pageUrl": {
          "type": "string",
          "format": "uri"
        },
        "historyStart": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time"
        },
        "truncated": {
          "type": "boolean",
          "description": "True when only the newest periods fit one response."
        },
        "notes": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "registrars": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "registrarId",
              "registrarName",
              "pageUrl",
              "listedNow",
              "current",
              "changes",
              "lowestCents",
              "highestCents",
              "steps"
            ],
            "properties": {
              "registrarId": {
                "type": "string"
              },
              "registrarName": {
                "type": "string"
              },
              "pageUrl": {
                "type": "string",
                "format": "uri"
              },
              "listedNow": {
                "type": "boolean"
              },
              "current": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "The current step, with the same fields as steps[]; null when the registrar does not list the extension now."
              },
              "changes": {
                "type": "integer",
                "minimum": 0
              },
              "lowestCents": {
                "type": "integer",
                "minimum": 0,
                "description": "Integer USD cents; 1000 = USD 10."
              },
              "highestCents": {
                "type": "integer",
                "minimum": 0,
                "description": "Integer USD cents; 1000 = USD 10."
              },
              "steps": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/HistoryStep"
                }
              },
              "pricingBasis": {
                "type": "string",
                "enum": [
                  "sampled_standard",
                  "sampled_api"
                ],
                "description": "The current saved offer’s sample label, as on price offers; absent for published price lists. Preserve it when quoting the current price."
              },
              "stale": {
                "type": "boolean",
                "description": "True when the source’s last refresh failed and its saved catalog is at least 24 hours old. Passing nextRefreshAt alone does not make prices stale; a scheduled refresh every 15 minutes replaces due catalogs."
              },
              "fetchedAt": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "When the current saved offer was retrieved."
              }
            }
          }
        }
      }
    }
  ],
  "$defs": {
    "Comparison": {
      "type": "object",
      "required": [
        "url",
        "omittedParameters"
      ],
      "description": "Revisit current standard prices with supported website filters. Omits proposed names and API pagination; does not save prices or availability.",
      "properties": {
        "url": {
          "type": "string",
          "format": "uri"
        },
        "omittedParameters": {
          "type": "array",
          "items": {
            "enum": [
              "years",
              "favorites"
            ]
          },
          "description": "Estimate horizons outside 2, 3, 5 and 10 years use the website default. Favorites are browser-local preferences and are never written by a link. Disclose omitted settings."
        }
      }
    },
    "Coverage": {
      "type": "object",
      "required": [
        "scope",
        "totalOffers",
        "freshOffers",
        "sources",
        "notCompared",
        "partial"
      ],
      "properties": {
        "scope": {
          "type": "string"
        },
        "totalOffers": {
          "type": "integer"
        },
        "freshOffers": {
          "type": "integer"
        },
        "partial": {
          "type": "boolean"
        },
        "error": {
          "type": [
            "string",
            "null"
          ]
        },
        "sources": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Source"
          }
        },
        "notCompared": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "url": {
                "type": "string"
              },
              "reason": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "Envelope": {
      "type": "object",
      "required": [
        "service",
        "apiVersion",
        "generatedAt",
        "currency",
        "coverage",
        "limits"
      ],
      "properties": {
        "service": {
          "$ref": "#/$defs/ServiceInformation"
        },
        "apiVersion": {
          "type": "string"
        },
        "generatedAt": {
          "type": "string",
          "format": "date-time"
        },
        "currency": {
          "const": "USD"
        },
        "availability": {
          "const": "not_checked"
        },
        "coverage": {
          "$ref": "#/$defs/Coverage"
        },
        "limits": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      }
    },
    "HistoryStep": {
      "type": "object",
      "required": [
        "from",
        "to",
        "cents",
        "approximate",
        "source",
        "firstRecord"
      ],
      "properties": {
        "from": {
          "type": "string",
          "format": "date-time",
          "description": "When this price was first seen."
        },
        "to": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time",
          "description": "When the next price or a removal was first seen; null while current."
        },
        "cents": {
          "type": "integer",
          "minimum": 0,
          "description": "Integer USD cents; 1000 = USD 10."
        },
        "regularCents": {
          "type": "integer",
          "minimum": 0,
          "description": "Published regular price while this price is a promotion."
        },
        "approximate": {
          "type": "boolean",
          "description": "Converted to USD from another currency."
        },
        "source": {
          "enum": [
            "refresh",
            "archive"
          ],
          "description": "refresh: our scheduled refresh (since 2026-09-28). archive: the registrar’s own price page as copied by the Internet Archive."
        },
        "changedAfter": {
          "type": "string",
          "format": "date-time",
          "description": "The previous price was last seen then: the change happened between this and from."
        },
        "firstRecord": {
          "type": "boolean",
          "description": "No earlier record; the price may be older than from."
        },
        "lastConfirmed": {
          "type": "string",
          "format": "date-time",
          "description": "Archived prices: the last archived copy still showing this price."
        },
        "archiveUrl": {
          "type": "string",
          "format": "uri"
        }
      }
    },
    "ServiceInformation": {
      "type": "object",
      "required": [
        "name",
        "url",
        "setupUrl",
        "pluginUrl"
      ],
      "description": "Cheapest Domains identity and optional setup links, separate from upstream evidence. Does not establish client installation or provider health.",
      "properties": {
        "name": {
          "const": "Cheapest Domains"
        },
        "url": {
          "type": "string",
          "format": "uri"
        },
        "setupUrl": {
          "type": "string",
          "format": "uri"
        },
        "pluginUrl": {
          "type": "string",
          "format": "uri"
        }
      }
    },
    "Source": {
      "type": "object",
      "required": [
        "id",
        "name",
        "url",
        "offerCount",
        "freshOfferCount",
        "stale",
        "refreshing"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "url": {
          "type": "string",
          "format": "uri"
        },
        "note": {
          "type": "string"
        },
        "offerCount": {
          "type": "integer"
        },
        "freshOfferCount": {
          "type": "integer"
        },
        "fetchedAt": {
          "type": [
            "string",
            "null"
          ]
        },
        "nextRefreshAt": {
          "type": [
            "string",
            "null"
          ],
          "description": "When the saved catalog is due for its next scheduled refresh; not an expiry of its prices."
        },
        "stale": {
          "type": "boolean",
          "description": "True when the source’s last refresh failed and its saved catalog is at least 24 hours old. Passing nextRefreshAt alone does not make prices stale; a scheduled refresh every 15 minutes replaces due catalogs."
        },
        "refreshing": {
          "type": "boolean"
        },
        "error": {
          "type": [
            "string",
            "null"
          ]
        },
        "refresh": {
          "type": "object",
          "required": [
            "phase",
            "completed",
            "total",
            "startedAt",
            "resumeAt"
          ],
          "description": "Progress of a staged catalog refresh, when one exists. Saved prices remain usable meanwhile.",
          "properties": {
            "phase": {
              "enum": [
                "queued",
                "running",
                "waiting",
                "paused",
                "complete"
              ]
            },
            "completed": {
              "type": "integer",
              "minimum": 0
            },
            "total": {
              "type": "integer",
              "minimum": 0
            },
            "startedAt": {
              "type": "integer",
              "description": "Unix time in milliseconds."
            },
            "resumeAt": {
              "type": "integer",
              "description": "Unix time in milliseconds."
            }
          }
        }
      }
    }
  }
}