{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://licencebook.vercel.app/contract/licence-v1.schema.json",
  "title": "licencebook licence contract v1",
  "description": "Requests to and answers from POST /api/licence/v1/check, /activate and /deactivate. contract/licence-v1.md is the prose; this file is the shape; test/contract.test.mjs holds the engine to both.",
  "$defs": {
    "request": {
      "type": "object",
      "additionalProperties": false,
      "required": ["key", "site", "product", "plugin_version"],
      "properties": {
        "key": { "type": "string", "pattern": "^lb_[0-9a-f]{40}$" },
        "site": { "type": "string", "maxLength": 253, "pattern": "^[a-z0-9-]{1,63}(\\.[a-z0-9-]{1,63})+$", "description": "A bare hostname: no scheme, no path, no port, lower case." },
        "product": { "enum": ["noticesolved-ai-act-pro", "noticesolved-cookie-consent-pro", "noticesolved-accessibility-pro", "aeowp-paid"] },
        "plugin_version": { "type": "string", "pattern": "^[0-9A-Za-z.+-]{1,32}$" }
      }
    },
    "granted": {
      "type": "object",
      "additionalProperties": false,
      "required": ["ok", "tier", "features", "seats", "sites_used", "expires_at", "token", "checked_at"],
      "properties": {
        "ok": { "const": true },
        "tier": { "type": "string" },
        "features": { "type": "array", "items": { "type": "string" } },
        "seats": { "type": ["integer", "null"], "minimum": 1, "description": "null means no seat limit." },
        "sites_used": { "type": ["integer", "null"], "minimum": 0, "description": "null when no activation store is connected; the answer then comes from Stripe alone." },
        "expires_at": { "type": "string", "format": "date-time" },
        "token": { "type": "string", "pattern": "^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]{86}$" },
        "checked_at": { "type": "string", "format": "date-time" }
      }
    },
    "refused": {
      "type": "object",
      "additionalProperties": false,
      "required": ["ok", "reason"],
      "properties": {
        "ok": { "const": false },
        "reason": { "enum": ["unknown_key", "revoked", "expired", "seat_limit", "product_mismatch"] }
      }
    },
    "released": {
      "type": "object",
      "additionalProperties": false,
      "required": ["ok", "sites_used"],
      "properties": { "ok": { "const": true }, "sites_used": { "type": "integer", "minimum": 0 } }
    },
    "token_payload": {
      "type": "object",
      "additionalProperties": false,
      "required": ["key_id", "product", "site", "tier", "features", "seats", "expires_at", "cache_until"],
      "properties": {
        "key_id": { "type": "string", "pattern": "^[0-9a-f]{16}$", "description": "The first 16 hex characters of sha256(key)." },
        "product": { "type": "string" },
        "site": { "type": "string" },
        "tier": { "type": "string" },
        "features": { "type": "array", "items": { "type": "string" } },
        "seats": { "type": ["integer", "null"] },
        "expires_at": { "type": "string", "format": "date-time" },
        "cache_until": { "type": "string", "format": "date-time" }
      }
    }
  },
  "oneOf": [{ "$ref": "#/$defs/granted" }, { "$ref": "#/$defs/refused" }, { "$ref": "#/$defs/released" }]
}
