{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://plans.hanzo.ai/plan.schema.json",
  "title": "Hanzo Plan",
  "description": "Envelope schema for a Hanzo plan record (subscription.json / plans.json / dns.json / blockchain.json). Adds the multi-tenant key `tenant_id`, the typed machine-readable `entitlements` block (the canonical vocabulary), and `price_ref` (the binding to recurring + metered billing). Display `features` and legacy `limits` remain OPTIONAL for backward compatibility; `entitlements` is the machine source of truth and supersedes `limits`.",
  "type": "object",
  "required": ["id", "name"],
  "properties": {
    "tenant_id": {
      "type": "string",
      "default": "hanzo",
      "title": "Owning tenant / reseller",
      "description": "The org/reseller that owns this plan catalog entry. Enables per-reseller catalogs. Plan ids are NO LONGER globally unique; the unique key is the pair (tenant_id, id). Defaults to \"hanzo\" (the first-party catalog) when omitted, so existing single-tenant records remain valid."
    },
    "id": {
      "type": "string",
      "title": "Plan id",
      "description": "Plan slug, unique WITHIN a tenant. e.g. \"pro\", \"world-pro\", \"dns-enterprise\"."
    },
    "name": { "type": "string", "title": "Display name" },
    "description": { "type": "string" },
    "category": { "type": "string" },
    "type": { "type": "string", "description": "Product family discriminator where present (e.g. \"rpc\", \"data\")." },

    "priceMonthly": { "type": ["number", "null"], "description": "Legacy convenience price (USD/month). Authoritative billing lives in price_ref." },
    "priceAnnual": { "type": ["number", "null"], "description": "Legacy convenience price (USD/month billed annually)." },
    "prices": { "type": "array", "items": { "type": "number" }, "description": "Every price the tier is sold at (USD/month), ascending, with prices[0] == priceMonthly. Absent for a tier sold at one price. A client renders these and sends back the INDEX it chose, never a price." },
    "priceHourly": { "type": ["number", "null"] },
    "pricePerUser": { "type": "boolean" },
    "popular": { "type": "boolean" },
    "freeTier": { "type": "boolean" },
    "contactSales": { "type": "boolean" },

    "features": {
      "type": "array",
      "items": { "type": "string" },
      "title": "Display features (human strings)",
      "description": "Marketing/display bullet strings. NOT machine-readable; do not gate on these. Kept for the pricing page. The machine truth is `entitlements`."
    },
    "limits": {
      "type": "object",
      "title": "Legacy limits (deprecated)",
      "description": "DEPRECATED ad-hoc limit keys. Retained for backward compatibility and as the back-compat source for `entitlements`. New consumers MUST read `entitlements`. entitlements.mjs#fromLegacy derives `entitlements` from this block + addons/payouts when `entitlements` is absent.",
      "additionalProperties": true
    },
    "addons": { "type": "object", "additionalProperties": true, "description": "Legacy boolean capability flags; folded into entitlements by fromLegacy." },
    "payouts": { "type": "object", "additionalProperties": true, "description": "Legacy revenue-share block; commerce.idle_resale_percent is derived from payouts.idleResalePercent." },
    "bundles": { "type": "array", "items": { "type": "string" }, "description": "Child plan slugs this plan grants free (zero-priced sub-subscriptions)." },
    "includedIn": { "type": "array", "items": { "type": "string" } },
    "tiers": { "type": "array", "description": "Sub-tier list for multi-tier products (blockchain data APIs)." },

    "entitlements": {
      "$ref": "https://plans.hanzo.ai/entitlements.schema.json",
      "title": "Canonical machine-readable entitlements",
      "description": "The typed entitlement block keyed by the canonical namespaced vocabulary. THE machine source of truth for what a plan grants. Read by commerce (to populate Entitlement), by the engine gate (via licensing token features derived from this), and by pricing (to attach price_ref)."
    },

    "price_ref": {
      "type": "object",
      "title": "Pricing binding (recurring + metered)",
      "description": "The contract link from a plan to billing. `recurring` is the flat subscription line; `metered` maps usage-based entitlement keys to per-unit meters. Resolved by hanzoai/pricing against live OpenRouter/HF/Zen+DO rates. The Stripe/commerce price ids are looked up by (tenant_id, plan_id, billing_period) so they are NOT hardcoded here.",
      "additionalProperties": false,
      "properties": {
        "currency": { "type": "string", "default": "USD" },
        "recurring": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "monthly_usd": { "type": ["number", "null"] },
            "annual_total_usd": { "type": ["number", "null"], "description": "USD charged ONCE for a year, and the authoritative annual price. It is stated rather than derived because the annual price is chosen, not computed: $199 and $999 are 12.72% and 15.91% off twelve months, and no single percentage produces both. annual_usd is this divided by twelve for display, which cannot always be exact — $199/12 is 16.5833… — so a bill is taken from here and a price tag from there." },
            "annual_usd": { "type": ["number", "null"], "description": "USD/month when billed annually — a DISPLAY figure derived from annual_total_usd, rounded to cents. Never bill from it: 16.58 x 12 is $198.96, four cents short of the $199 actually charged." },
            "per_seat": { "type": "boolean", "default": false, "description": "true when the recurring price multiplies by seat count (mirrors pricePerUser)." },
            "stripe_lookup_key": { "type": ["string", "null"], "description": "Optional stable Stripe lookup key; when null, commerce resolves by (tenant_id, id, period)." }
          }
        },
        "metered": {
          "type": "array",
          "description": "Usage meters that bill on top of the recurring line. Each entry binds a canonical entitlement key to a unit price source.",
          "items": {
            "type": "object",
            "required": ["entitlement", "unit"],
            "additionalProperties": false,
            "properties": {
              "entitlement": {
                "type": "string",
                "description": "Canonical entitlement key this meter prices (e.g. \"ai.tokens_per_min\" overage, \"cloud.included_credits_usd\" overage, \"tools.web_search\", \"rpc.compute_units_monthly\")."
              },
              "unit": {
                "type": "string",
                "description": "Billing unit, e.g. \"per_mtok\", \"per_query\", \"per_image\", \"per_gb_month\", \"per_million_cu\", \"per_vm_hour\"."
              },
              "source": {
                "type": "string",
                "enum": ["zen-gateway", "openrouter", "huggingface", "digitalocean", "tools.json", "storage.json", "static"],
                "description": "Where pricing.hanzo.ai resolves the live unit price from (markup applied per pricing-policy.json)."
              },
              "included": {
                "type": ["number", "null"],
                "description": "Quantity included in the recurring price before metering begins; -1 = unlimited (no metering)."
              },
              "overage_usd": {
                "type": ["number", "null"],
                "description": "Static per-unit overage price when source=static (e.g. blockchain.json overagePerMillion). null = resolved live from source."
              }
            }
          }
        }
      }
    }
  },
  "additionalProperties": true
}
