{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://plans.hanzo.ai/entitlements.schema.json",
  "title": "Hanzo Canonical Entitlement Vocabulary",
  "description": "The ONE machine-readable entitlement vocabulary shared across the Hanzo monetization suite (plans -> pricing -> commerce -> licensing -> engine). Keys are namespaced (ai.* / cloud.* / licensing.* / world.* / dns.* / rpc.* / data.* / tools.* / commerce.*). Each key has a fixed type + unit so a number means the same thing everywhere. Defined once HERE in hanzoai/plans; never redefined downstream. A value of -1 on any numeric quota means UNLIMITED. A value of null means 'unset / contact sales / inherit'. This object is the typed `entitlements` block of a plan; the engine-facing flat capability list is derived from it (see entitlements.mjs#toLicenseFeatures).",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "ai.tokens_per_min": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "tokens/minute",
      "title": "AI token throughput",
      "description": "Max LLM tokens (prompt+completion) per minute across all models. -1 = unlimited. Back-compat source: limits.tokensPerMinute."
    },
    "ai.requests_per_min": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "requests/minute",
      "title": "AI request rate",
      "description": "Max inference API requests per minute. -1 = unlimited. Back-compat source: limits.requestsPerMinute."
    },
    "ai.requests_per_hour": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "requests/hour",
      "title": "AI requests per hour",
      "description": "Max inference API requests in an hour. The volume a plan includes, which is a different question from ai.requests_per_min: that bounds a burst, this bounds how much of the plan is used up. Shorter windows refresh as time passes; the longer ones are the ceiling. -1 = unlimited. Back-compat source: limits.requestsPerHour."
    },
    "ai.requests_per_day": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "requests/day",
      "title": "AI requests per day",
      "description": "Max inference API requests in a day. The volume a plan includes, which is a different question from ai.requests_per_min: that bounds a burst, this bounds how much of the plan is used up. Shorter windows refresh as time passes; the longer ones are the ceiling. -1 = unlimited. Back-compat source: limits.requestsPerDay."
    },
    "ai.requests_per_week": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "requests/week",
      "title": "AI requests per week",
      "description": "Max inference API requests in a week. The volume a plan includes, which is a different question from ai.requests_per_min: that bounds a burst, this bounds how much of the plan is used up. Shorter windows refresh as time passes; the longer ones are the ceiling. -1 = unlimited. Back-compat source: limits.requestsPerWeek."
    },
    "ai.requests_per_month": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "requests/month",
      "title": "AI requests per month",
      "description": "Max inference API requests in a month. The volume a plan includes, which is a different question from ai.requests_per_min: that bounds a burst, this bounds how much of the plan is used up. Shorter windows refresh as time passes; the longer ones are the ceiling. -1 = unlimited. Back-compat source: limits.requestsPerMonth."
    },
    "ai.models": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "x-unit": "model-id|tier-token",
      "title": "Allowed AI models",
      "description": "Model access grants. Either explicit Zen model ids (e.g. \"zen3-omni\") or tier tokens that expand to a model set: \"tier:starter\" | \"tier:pro\" | \"tier:max\" | \"tier:ultra\". The wildcard \"*\" (often written as the grant \"premium\") means unlimited premium model access (Zen, Claude, GPT-4o, etc.)."
    },
    "ai.agents": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "count",
      "title": "Included agents",
      "description": "Agents the plan includes on the roster. An agent beyond this bills at the agent seat rate (seats.json monthly.agent). -1 = unlimited. Back-compat source: limits.agents."
    },
    "ai.bots": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "count",
      "title": "Included bots",
      "description": "Persistent bots the plan includes on the roster. A bot beyond this bills at the bot seat rate (seats.json monthly.bot), which buys the base compute tier. -1 = unlimited. Back-compat source: limits.bots."
    },
    "ai.premium_models": {
      "type": "boolean",
      "title": "Premium model access",
      "description": "true grants unlimited access to premium/frontier models. Back-compat source: addons.premiumModels."
    },
    "ai.priority_inference": {
      "type": "boolean",
      "title": "Priority inference queue",
      "description": "true routes the holder's requests to the priority inference lane."
    },
    "ai.custom_training": {
      "type": "boolean",
      "title": "Custom model fine-tuning",
      "description": "true permits custom fine-tuning / training jobs. Back-compat source: addons.customTraining."
    },
    "ai.free_credit_usd": {
      "type": [
        "number",
        "null"
      ],
      "minimum": 0,
      "x-unit": "USD",
      "title": "Included one-time AI credit",
      "description": "One-time free inference credit in USD granted on signup. Back-compat source: limits.freeCredit."
    },
    "cloud.max_vms": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "count",
      "title": "Max concurrent VMs",
      "description": "Maximum number of concurrently running cloud VMs/instances. -1 = unlimited. Back-compat source: plans.json maxVMs."
    },
    "cloud.vcpus": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": 0,
      "x-unit": "vCPU",
      "title": "vCPUs per instance",
      "description": "vCPU count for the plan's base instance. Back-compat source: plans.json vcpus."
    },
    "cloud.cpu_class": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "shared",
        "dedicated",
        null
      ],
      "title": "CPU class",
      "description": "Whether vCPUs are shared or dedicated. Back-compat source: plans.json cpuType."
    },
    "cloud.memory_gb": {
      "type": [
        "number",
        "null"
      ],
      "minimum": 0,
      "x-unit": "GB",
      "title": "Memory per instance",
      "description": "RAM in GB for the plan's base instance. Back-compat source: plans.json memoryGB."
    },
    "cloud.disk_gb": {
      "type": [
        "number",
        "null"
      ],
      "minimum": 0,
      "x-unit": "GB",
      "title": "Disk per instance",
      "description": "Root/block disk in GB. Back-compat source: plans.json diskGB."
    },
    "cloud.transfer_tb": {
      "type": [
        "number",
        "null"
      ],
      "minimum": -1,
      "x-unit": "TB/month",
      "title": "Egress transfer",
      "description": "Included monthly data transfer in TB. -1 = unlimited. Back-compat source: plans.json transferTB."
    },
    "cloud.gpu_class": {
      "type": [
        "string",
        "null"
      ],
      "x-unit": "gpu-tier-id",
      "title": "GPU class",
      "description": "GPU tier id the plan may schedule (matches gpu.json ids, e.g. \"gpu-h100x1-80gb\"), or null for no GPU. The literal \"*\" means any GPU class."
    },
    "cloud.included_credits_usd": {
      "type": [
        "number",
        "null"
      ],
      "minimum": 0,
      "x-unit": "USD/month",
      "title": "Included monthly cloud credits",
      "description": "Recurring cloud credit allowance in USD/month. Back-compat sources: limits.includedCloudCredits, limits.includedCloudCreditsPerUser (per seat)."
    },
    "cloud.storage_gb": {
      "type": [
        "number",
        "null"
      ],
      "minimum": 0,
      "x-unit": "GB",
      "title": "Block storage allowance",
      "description": "Included block storage in GB."
    },
    "licensing.app_ids": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "hanzo",
          "lux",
          "zoo",
          "hanzo-dev"
        ]
      },
      "x-unit": "app-id",
      "title": "Licensed app builds",
      "description": "Engine app builds this plan licenses. Maps to the license token `app_id` claim (licensing/token.go: License.AppID) which the engine verifies against EXPECTED_APP_ID at boot."
    },
    "licensing.product_ids": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "x-unit": "commerce-sku",
      "title": "Licensed products",
      "description": "Commerce SKUs / proprietary products this plan entitles (e.g. \"engine\", \"team\", plugin ids). Maps to commerce Entitlement.ProductID; gates token issuance in licensing/handlers.go. A product is a thing sold, never a build of it: engine ships CUDA, ROCm and Metal from one SKU, so an accelerator is never a product id here."
    },
    "licensing.seats": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "count",
      "title": "Licensed seats",
      "description": "Number of named users/devices the license covers. -1 = unlimited. Back-compat sources: limits.maxMembers, limits.maxSeats."
    },
    "licensing.offline_grace_days": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": 0,
      "x-unit": "days",
      "title": "Offline grace period",
      "description": "Days the engine will run offline before requiring a fresh token refresh."
    },
    "licensing.engine_features": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "x-unit": "feature-token",
      "title": "Engine feature grants",
      "description": "Capability tokens copied verbatim into the license token `features` list (licensing: License.Features) and matched by the engine release gate (licensing/releases.go: Release.MinFeatures via hasFeatures). Canonical tokens: \"inference\", \"embeddings\", \"rerank\", \"training\", \"vision\", \"audio\", \"tools\". Namespaced grants emitted by toLicenseFeatures (e.g. \"ai.premium\", \"licensing.app:hanzo\") are appended automatically."
    },
    "world.max_alerts": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "count",
      "title": "Saved alerts",
      "description": "Max saved OSINT alert rules. -1 = unlimited. Back-compat source: limits.maxAlerts."
    },
    "world.api_rate_limit": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "requests/minute",
      "title": "World API rate",
      "description": "Hanzo World REST API rate limit. -1 = unlimited. Back-compat source: limits.apiRateLimit."
    },
    "world.mcp_rate_limit": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "requests/minute",
      "title": "World MCP rate",
      "description": "Hanzo World MCP API rate limit. -1 = unlimited. Back-compat source: limits.mcpRateLimit."
    },
    "dns.zones": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "count",
      "title": "DNS zones",
      "description": "Max DNS zones. -1 = unlimited. Back-compat source: limits.zones."
    },
    "dns.records_per_zone": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "count",
      "title": "Records per DNS zone",
      "description": "Max records per zone. -1 = unlimited. Back-compat source: limits.recordsPerZone."
    },
    "dns.queries_per_day": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "queries/day",
      "title": "DNS query volume",
      "description": "Max DNS queries per day. -1 = unlimited. Back-compat source: limits.queriesPerDay."
    },
    "rpc.compute_units_monthly": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "compute-units/month",
      "title": "Blockchain RPC compute units",
      "description": "Included blockchain RPC compute units per month. -1 = unlimited. Back-compat source: limits.computeUnitsMonthly."
    },
    "data.requests_monthly": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "requests/month",
      "title": "Data API requests",
      "description": "Included data-API requests per month (token/nft/wallet APIs). -1 = unlimited. Back-compat source: tiers[].requestsMonthly."
    },
    "tools.web_search": {
      "type": "boolean",
      "title": "Web search tool",
      "description": "true enables the metered web-search tool (priced via tools.json / price_ref.metered)."
    },
    "tools.code_interpreter": {
      "type": "boolean",
      "title": "Code interpreter tool",
      "description": "true enables the metered code-interpreter tool."
    },
    "tools.image_generation": {
      "type": "boolean",
      "title": "Image generation tool",
      "description": "true enables the metered image-generation tool."
    },
    "tools.speech": {
      "type": "boolean",
      "title": "Speech tools",
      "description": "true enables metered speech-to-text and text-to-speech tools."
    },
    "commerce.members": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "count",
      "title": "Org members",
      "description": "Max members/seats on the billing org. -1 = unlimited. Back-compat source: limits.maxMembers. (Distinct from licensing.seats, which counts engine-licensed devices.)"
    },
    "team.guests": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": -1,
      "x-unit": "count",
      "title": "Team workspace guests",
      "description": "Max guests a seat-holder may invite into their hanzo.team workspace without paying for a seat. -1 = unlimited. Read by cloud/apps/team ReadPlan as planInfo.guestLimit. Distinct from commerce.members, which is the seats the org pays for."
    },
    "commerce.sso": {
      "type": "boolean",
      "title": "SSO / SAML",
      "description": "true enables SSO/SAML via Hanzo IAM. Back-compat source: addons.sso."
    },
    "commerce.shared_billing": {
      "type": "boolean",
      "title": "Shared billing",
      "description": "true enables pooled/shared billing across the org. Back-compat source: addons.sharedBilling."
    },
    "commerce.audit_log": {
      "type": "boolean",
      "title": "Audit log",
      "description": "true enables the org audit log."
    },
    "commerce.support_tier": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "community",
        "email",
        "priority",
        "dedicated",
        null
      ],
      "title": "Support tier",
      "description": "Support level granted by the plan."
    },
    "commerce.sla": {
      "type": [
        "string",
        "null"
      ],
      "x-unit": "percent-uptime",
      "title": "SLA",
      "description": "Contracted uptime SLA as a string (e.g. \"99.99%\"), or null if none."
    },
    "commerce.idle_resale_percent": {
      "type": [
        "number",
        "null"
      ],
      "minimum": 0,
      "maximum": 100,
      "x-unit": "percent",
      "title": "Idle resale revenue share",
      "description": "Percent the holder earns reselling idle compute/LLM resources. Back-compat source: payouts.idleResalePercent."
    },
    "commerce.included_credit_usd": {
      "type": [
        "number",
        "null"
      ],
      "minimum": 0,
      "x-unit": "USD/month",
      "title": "Included monthly usage credit",
      "description": "Recurring usage-credit allowance in USD/month that commerce grants to the tenant's prepaid balance at each billing-period start (non-accumulating; the unused remainder expires at period end). The Hanzo gateway's prepaid balance gate honors it transparently because the grant lands as an expiring balance deposit; usage burns it down first, and overage draws down purchased balance / is billed via the payment processor. Distinct from ai.free_credit_usd (a one-time signup grant) and cloud.included_credits_usd (DigitalOcean VM credit). 0 = no included usage. Back-compat source: limits.includedCreditUsd."
    },
    "commerce.on_prem": {
      "type": "boolean",
      "title": "On-prem / air-gapped deployment",
      "description": "true permits on-premise or air-gapped deployment."
    }
  }
}
