{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://stackcut.io/exits/schema.json",
  "title": "Stackcut exit packet",
  "description": "How to cancel one digital subscription: every route by billing channel, what it does, what to export first, cheaper ways to keep it, and notes for an agent. Every fact cites a vendor, platform or regulator page with a verbatim quote and a check date. CC BY 4.0, https://stackcut.io/cancel. Negotiation options are sourced extensions of existing billing routes; only independently reviewed packets are published.",
  "type": "object",
  "required": [
    "id",
    "name",
    "category",
    "website",
    "summary",
    "routes",
    "sources",
    "verification"
  ],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9_]{1,48}$"
    },
    "name": {
      "type": "string"
    },
    "category": {
      "enum": [
        "streaming_video",
        "music_audio",
        "news_magazines",
        "dating",
        "cloud_storage",
        "productivity",
        "ai_tools",
        "design_creative",
        "developer_tools",
        "marketing_sales",
        "communication",
        "security_privacy",
        "education_learning",
        "health_fitness",
        "gaming",
        "shopping_memberships",
        "finance_apps",
        "other_digital",
        "customer_support",
        "business_finance"
      ]
    },
    "website": {
      "type": "string",
      "format": "uri"
    },
    "domains": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "The service's own domains, lowercase, no www"
    },
    "summary": {
      "type": "string",
      "description": "How to cancel and the one gotcha, in at most 50 words"
    },
    "routes": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "required": [
          "channel",
          "applies_to",
          "who",
          "method",
          "steps",
          "source_ids"
        ],
        "properties": {
          "channel": {
            "enum": [
              "direct_web",
              "direct_app",
              "apple_app_store",
              "google_play",
              "amazon",
              "roku",
              "paypal",
              "carrier_or_isp",
              "reseller_or_partner",
              "sales_contract"
            ],
            "description": "Who bills the subscription"
          },
          "label": {
            "type": "string",
            "maxLength": 48,
            "description": "Optional short heading for this route, used when a service has several routes on the same channel (e.g. \"Billed by Verizon\", \"Team plans\")"
          },
          "applies_to": {
            "type": "string",
            "description": "Which plans or accounts this route covers"
          },
          "who": {
            "type": "string",
            "description": "Who can cancel (account holder, workspace owner, billing admin)"
          },
          "method": {
            "enum": [
              "self_serve",
              "email",
              "chat",
              "phone",
              "form",
              "letter",
              "contact_sales",
              "platform_settings"
            ]
          },
          "start_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The official page where the flow starts"
          },
          "steps": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": [
                "do"
              ],
              "properties": {
                "do": {
                  "type": "string",
                  "description": "What to do, in plain English"
                },
                "expect": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Words the person should see next, as the help page names them"
                }
              }
            }
          },
          "confirm_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "What proves it worked"
          },
          "effective": {
            "type": [
              "string",
              "null"
            ],
            "description": "When access ends"
          },
          "notice": {
            "type": [
              "string",
              "null"
            ],
            "description": "Notice needed before renewal"
          },
          "refund": {
            "type": [
              "string",
              "null"
            ]
          },
          "fees": {
            "type": [
              "string",
              "null"
            ],
            "description": "Early-termination or other fees"
          },
          "source_ids": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string"
            }
          }
        }
      }
    },
    "pay_less": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "type",
          "what",
          "evidence",
          "source_ids"
        ],
        "properties": {
          "type": {
            "enum": [
              "pause",
              "downgrade",
              "free_plan",
              "ad_supported_plan",
              "annual_billing",
              "discount_program",
              "retention_offer",
              "seat_reduction",
              "bundle",
              "student_or_nonprofit"
            ]
          },
          "what": {
            "type": "string"
          },
          "evidence": {
            "enum": [
              "documented",
              "reported"
            ],
            "description": "documented = on the vendor's own page; reported = users report it (community source)"
          },
          "seen": {
            "type": [
              "string",
              "null"
            ],
            "description": "YYYY-MM, for reported offers"
          },
          "source_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      }
    },
    "export": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "data",
          "how"
        ],
        "properties": {
          "data": {
            "type": "string"
          },
          "how": {
            "type": "string"
          },
          "format": {
            "type": [
              "string",
              "null"
            ]
          },
          "limits": {
            "type": [
              "string",
              "null"
            ]
          },
          "source_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      }
    },
    "after_cancel": {
      "type": "object",
      "properties": {
        "access_until": {
          "type": [
            "string",
            "null"
          ]
        },
        "data_kept": {
          "type": [
            "string",
            "null"
          ]
        },
        "you_lose": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      }
    },
    "agent_notes": {
      "type": "object",
      "properties": {
        "sign_in_needed": {
          "type": [
            "boolean",
            "null"
          ]
        },
        "phone_or_chat_only": {
          "type": [
            "boolean",
            "null"
          ]
        },
        "retention_step": {
          "type": [
            "string",
            "null"
          ]
        },
        "tips": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      }
    },
    "delete_account": {
      "type": "object",
      "description": "Added at build time from JustDeleteMe (MIT): deleting an account is not cancelling a plan",
      "properties": {
        "url": {
          "type": [
            "string",
            "null"
          ]
        },
        "difficulty": {
          "type": [
            "string",
            "null"
          ]
        },
        "notes": {
          "type": [
            "string",
            "null"
          ]
        },
        "source": {
          "type": "string"
        }
      }
    },
    "sources": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "required": [
          "id",
          "url",
          "publisher",
          "quote"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "publisher": {
            "enum": [
              "vendor",
              "platform",
              "regulator",
              "community"
            ]
          },
          "quote": {
            "type": [
              "string",
              "null"
            ],
            "description": "Verbatim, at most 30 words"
          },
          "supports": {
            "type": "string"
          }
        }
      }
    },
    "verification": {
      "type": "object",
      "required": [
        "level",
        "checked_on",
        "review"
      ],
      "properties": {
        "level": {
          "enum": [
            "docs",
            "walked",
            "partial",
            "legacy"
          ],
          "description": "docs = every route fact from official pages; walked = followed on a real account; partial = something important unconfirmed"
        },
        "checked_on": {
          "type": "string",
          "format": "date"
        },
        "by": {
          "type": "string"
        },
        "unverified": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "review": {
          "type": "object",
          "required": [
            "by",
            "on",
            "result"
          ],
          "description": "A second, independent check of the packet against the official pages",
          "properties": {
            "by": {
              "type": "string"
            },
            "on": {
              "type": "string",
              "format": "date"
            },
            "result": {
              "enum": [
                "ok",
                "fixed",
                "partial"
              ],
              "description": "ok = nothing to change; fixed = issues found and corrected; partial = some facts couldn't be re-checked (listed in unverified)"
            },
            "notes": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "What the review changed, briefly"
            }
          }
        }
      }
    },
    "negotiation": {
      "type": "array",
      "description": "Optional ways to ask for a lower bill while deciding whether to keep the service. Eligibility is account-specific and never guaranteed.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "route_id",
          "kind",
          "channel",
          "method",
          "applies_to",
          "market",
          "start_url",
          "contact_phone",
          "contact_email",
          "evidence",
          "checked_on",
          "seen_on",
          "source_ids",
          "terms",
          "how",
          "unknowns"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-z0-9_-]+\\.[a-z0-9_-]+$",
            "description": "Stable service.action identifier."
          },
          "route_id": {
            "type": "string",
            "pattern": "^[a-z0-9-]+--[a-z0-9-]+--r[1-9][0-9]*$",
            "description": "Stable ID of an existing billing route: <service>--<channel>--r<one-based route index>."
          },
          "kind": {
            "enum": [
              "cancel_flow_offer",
              "support_retention",
              "renewal_negotiation",
              "contract_rightsize"
            ]
          },
          "channel": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "direct_web",
              "direct_app",
              "apple_app_store",
              "google_play",
              "amazon",
              "roku",
              "paypal",
              "carrier_or_isp",
              "reseller_or_partner",
              "sales_contract",
              null
            ]
          },
          "method": {
            "enum": [
              "self_serve",
              "email",
              "chat",
              "phone",
              "form",
              "letter",
              "contact_sales",
              "platform_settings"
            ]
          },
          "applies_to": {
            "type": "string",
            "minLength": 1
          },
          "market": {
            "type": [
              "string",
              "null"
            ]
          },
          "start_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "contact_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "contact_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "evidence": {
            "enum": [
              "documented",
              "reported",
              "walked"
            ]
          },
          "checked_on": {
            "type": "string",
            "format": "date"
          },
          "seen_on": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "source_ids": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "type": "string"
            }
          },
          "terms": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "price",
              "currency",
              "billing_period",
              "discount_duration_months",
              "renewal_price",
              "new_commitment",
              "fees",
              "starts"
            ],
            "properties": {
              "price": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^[A-Z]{3}$"
              },
              "billing_period": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "month",
                  "year",
                  null
                ]
              },
              "discount_duration_months": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 1
              },
              "renewal_price": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0
              },
              "new_commitment": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "fees": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0
              },
              "starts": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "how": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "unknowns": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          }
        }
      }
    },
    "evidence": {
      "type": "object",
      "additionalProperties": false,
      "description": "Generated read-only source freshness and stable route identity. It does not record account eligibility or successful actions.",
      "required": [
        "packet_revision",
        "docs_checked_on",
        "last_observed_success_on",
        "verification",
        "freshness",
        "routes"
      ],
      "properties": {
        "packet_revision": {
          "type": "string",
          "pattern": "^[a-f0-9]{64}$"
        },
        "docs_checked_on": {
          "type": [
            "string",
            "null"
          ],
          "format": "date"
        },
        "last_observed_success_on": {
          "type": [
            "string",
            "null"
          ],
          "format": "date"
        },
        "verification": {
          "enum": [
            "docs",
            "walked",
            "partial",
            "legacy"
          ]
        },
        "freshness": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "status",
            "checked_on",
            "age_days",
            "stale_after_days",
            "next_check_on"
          ],
          "properties": {
            "status": {
              "enum": [
                "current",
                "stale",
                "unknown"
              ]
            },
            "checked_on": {
              "type": [
                "string",
                "null"
              ],
              "format": "date"
            },
            "age_days": {
              "type": [
                "integer",
                "null"
              ]
            },
            "stale_after_days": {
              "type": "integer",
              "minimum": 1
            },
            "next_check_on": {
              "type": [
                "string",
                "null"
              ],
              "format": "date"
            }
          }
        },
        "routes": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "route_id",
              "channel",
              "checked_on",
              "freshness"
            ],
            "properties": {
              "route_id": {
                "type": "string"
              },
              "channel": {
                "enum": [
                  "direct_web",
                  "direct_app",
                  "apple_app_store",
                  "google_play",
                  "amazon",
                  "roku",
                  "paypal",
                  "carrier_or_isp",
                  "reseller_or_partner",
                  "sales_contract"
                ]
              },
              "checked_on": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date"
              },
              "freshness": {
                "$ref": "#/properties/evidence/properties/freshness"
              }
            }
          }
        }
      }
    }
  }
}
