{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://releeze.app/spec/product.schema.json",
  "title": "releeze Product",
  "description": "A product profile. Created once per product, then kept current by the feature_deltas of each release. This is the object an agent answers from when it searches releeze, so current_features must describe what the product can do today — not what it launched with.",
  "type": "object",
  "additionalProperties": false,
  "required": ["slug", "name", "tagline", "categories", "platforms", "links", "logo", "current_features"],
  "properties": {
    "slug": {
      "type": "string",
      "description": "URL identity for the profile. Derive it from the repository name unless the maker overrides it. Immutable once published.",
      "pattern": "^[a-z0-9][a-z0-9-]{1,38}[a-z0-9]$"
    },
    "name": {
      "type": "string",
      "description": "The product's own name, capitalised as the maker writes it.",
      "minLength": 2,
      "maxLength": 40
    },
    "tagline": {
      "type": "string",
      "description": "One line, sentence case, no trailing period required. What the product is for, not how it is built.",
      "minLength": 10,
      "maxLength": 80
    },
    "description": {
      "type": "string",
      "description": "Two to four sentences for the profile header. Written for someone who has never heard of the product.",
      "maxLength": 500
    },
    "categories": {
      "type": "array",
      "description": "One to three categories. Drives the category feeds and the category filter in search_products.",
      "minItems": 1,
      "maxItems": 3,
      "uniqueItems": true,
      "items": {
        "enum": [
          "AI tools",
          "Productivity",
          "Design tools",
          "Developer tools",
          "Analytics",
          "No-code",
          "Marketing",
          "Finance",
          "Education",
          "Health"
        ]
      }
    },
    "platforms": {
      "type": "array",
      "description": "Where the product actually runs. An agent filters on this before recommending anything.",
      "minItems": 1,
      "uniqueItems": true,
      "items": { "enum": ["web", "ios", "android", "desktop", "cli", "mcp", "api", "extension"] }
    },
    "links": {
      "type": "object",
      "description": "At least one link must resolve. capture_screens uses `website` as its default target when no explicit target is given.",
      "additionalProperties": false,
      "properties": {
        "website": { "type": "string", "format": "uri" },
        "repo": { "type": "string", "format": "uri" },
        "app_store": { "type": "string", "format": "uri" },
        "docs": { "type": "string", "format": "uri" }
      },
      "anyOf": [
        { "properties": { "website": { "type": "string", "format": "uri" } }, "required": ["website"] },
        { "properties": { "repo": { "type": "string", "format": "uri" } }, "required": ["repo"] },
        { "properties": { "app_store": { "type": "string", "format": "uri" } }, "required": ["app_store"] }
      ]
    },
    "logo": {
      "type": "object",
      "description": "Square, at least 512x512. Uploaded through upload_asset with asset_type \"logo\"; the returned filename belongs in `file`.",
      "additionalProperties": false,
      "required": ["file"],
      "properties": {
        "file": { "type": "string", "minLength": 1 },
        "width": { "type": "integer", "minimum": 512 },
        "height": { "type": "integer", "minimum": 512 }
      }
    },
    "current_features": {
      "type": "array",
      "description": "What the product can do right now, three to ten short lines. Each release's feature_deltas add to and remove from this list, which is what keeps a profile from going stale.",
      "minItems": 3,
      "maxItems": 10,
      "uniqueItems": true,
      "items": { "type": "string", "minLength": 6, "maxLength": 80 }
    },
    "pricing": {
      "enum": ["free", "freemium", "paid", "open-source"],
      "description": "Coarse on purpose. An agent needs to filter on it; nobody needs a price table in a feed."
    },
    "team": {
      "type": "array",
      "description": "Who to credit, one to eight people, in the order the credit should read. This is the people who actually built and shipped the product, not everyone at the company — a name nobody would recognise from the work does not belong here. Each entry needs a `name`, the one the person signs their work with, and may add a `role` and a `url` pointing at their own site or profile rather than the product's. The profile is still the product's, not any of these people's.",
      "minItems": 1,
      "maxItems": 8,
      "uniqueItems": true,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["name"],
        "properties": {
          "name": { "type": "string", "minLength": 1, "maxLength": 60 },
          "role": {
            "type": "string",
            "description": "What this person did on this product, in a few words: \"design\", \"the sync engine\", \"everything else\". Free text, not a list to pick from — a two-person project has no job titles worth borrowing. Leave it out rather than inventing one.",
            "minLength": 2,
            "maxLength": 40
          },
          "url": { "type": "string", "format": "uri" }
        }
      }
    },
    "agent": {
      "type": "object",
      "description": "Which agent operates this profile. Set by the MCP server from the calling client; not maker-editable.",
      "additionalProperties": false,
      "required": ["client"],
      "properties": {
        "client": { "type": "string", "examples": ["Claude Code", "Codex"] },
        "connected_at": { "type": "string", "format": "date-time" }
      }
    },
    "visibility": {
      "enum": ["draft", "published"],
      "default": "draft",
      "description": "A profile stays in draft until the maker publishes it. Drafts are never in a feed and never returned by search_products."
    }
  }
}
