{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://www.cancelatlas.com/api/v1/schema.json",
  "title": "Cancel Atlas company record",
  "description": "One company × jurisdiction record in the Cancel Atlas Cancellation Friction Index. The dataset (api/v1/companies.json) is { api_version, methodology_version, generated, total_count, license, license_url, policy_weights, flow_weights, policy_dimensions, flow_dimensions, companies[] }; each item validates against this schema.",
  "type": "object",
  "required": ["id", "name", "industry", "jurisdiction", "policy", "flow"],
  "properties": {
    "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$", "description": "Stable kebab-case identifier." },
    "name": { "type": "string" },
    "industry": { "type": "string", "description": "One of the published industry verticals." },
    "jurisdiction": { "type": "string", "description": "Country/region code (e.g. US, UK, DE, EU)." },
    "join_clicks": { "type": ["integer", "null"], "description": "Best estimate of clicks to subscribe (asymmetry context)." },
    "cancellation_types": { "type": "array", "items": { "type": "string", "enum": ["online", "app", "email", "phone", "chat", "mail", "in_person"] } },
    "pause": { "type": "object", "properties": { "available": { "type": "boolean" }, "detail": { "type": "string" } } },
    "data_deletion": { "type": "object", "properties": { "detail": { "type": "string" }, "url": { "type": ["string", "null"], "format": "uri" } } },
    "links": { "type": "object", "description": "Official policy pages for this company (cancel = policy_url; refund/terms/privacy from cited sources, null when none is published/cited). Informational — never affects the score.", "properties": { "cancel": { "type": ["string", "null"], "format": "uri" }, "refund": { "type": ["string", "null"], "format": "uri" }, "terms": { "type": ["string", "null"], "format": "uri" }, "privacy": { "type": ["string", "null"], "format": "uri" } } },
    "policy": {
      "type": "object",
      "description": "Documented-Policy tier — what the company publishes.",
      "required": ["evidence_tier", "scores", "score", "grade", "policy_url", "policy_summary", "policy_checked", "sources"],
      "properties": {
        "evidence_tier": { "type": "string", "enum": ["documented", "crowd_verified", "editor_verified"] },
        "scores": {
          "type": "object",
          "required": ["cancel_url", "channel_disclosed", "notice_disclosed", "refund_disclosed", "findability"],
          "additionalProperties": { "type": "integer", "minimum": 0, "maximum": 100 }
        },
        "score": { "type": "integer", "minimum": 0, "maximum": 100, "description": "Weighted composite (round(Σ weight_k/100 × dimension_k)); reproducible from policy_weights." },
        "grade": { "type": "string", "enum": ["A", "B", "C", "D", "F"], "description": "A≥85 B≥70 C≥55 D≥40 F<40." },
        "cancel_url": { "type": "null", "description": "Reserved; the link lives in policy_url + sources." },
        "policy_url": { "type": "string", "format": "uri" },
        "policy_summary": { "type": "string" },
        "policy_checked": { "type": "string", "format": "date" },
        "method": { "type": "string" },
        "sources": { "type": "array", "minItems": 1, "items": { "type": "object", "required": ["title", "url"], "properties": { "title": { "type": "string" }, "url": { "type": "string", "format": "uri" } } } }
      }
    },
    "flow": {
      "description": "Verified-Flow (behavioural) tier — null until a verified behavioural audit exists.",
      "type": ["object", "null"]
    }
  }
}
