{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://token-marketplace.example/schemas/provider-manifest-v1.json",
  "title": "tm provider manifest v1",
  "description": "The document a provider publishes to be listed (M3, HANDOFF §6.2). Authoritative validator: packages/conformance/src/manifest.ts — this schema is published for provider-side tooling and MUST stay in sync with it. The validator is the authority: no CI job checks this file against providers/*.json yet.",
  "type": "object",
  "required": ["manifest_version", "provider", "endpoint", "settlement", "models"],
  "additionalProperties": true,
  "properties": {
    "manifest_version": { "const": "1" },
    "provider": {
      "type": "object",
      "required": ["id", "name", "privacy_policy_url", "prompt_logging"],
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
          "description": "Deployment id, ledger attribution key, settlement counterparty."
        },
        "name": { "type": "string", "minLength": 1 },
        "privacy_policy_url": { "type": "string", "format": "uri", "pattern": "^https?://" },
        "terms_of_service_url": { "type": "string", "format": "uri", "pattern": "^https?://" },
        "status_page_url": { "type": "string", "format": "uri", "pattern": "^https?://" },
        "priority": {
          "type": "integer", "minimum": 0, "maximum": 100,
          "description": "Routing preference across providers of the same model: lower is tried first (0 = first-party lab, 1 = aggregator fallback). Absent = 0."
        },
        "prompt_logging": {
          "enum": ["none", "retained"],
          "description": "Does the provider retain prompts? Disclosed to buyers."
        },
        "listing": {
          "enum": ["public", "private"],
          "description": "D26: \"private\" = dedicated capacity for one dedicated endpoint; compiled but never routed, listed or priced publicly. Absent = \"public\"."
        }
      }
    },
    "endpoint": {
      "type": "object",
      "required": ["base_url", "dialect"],
      "properties": {
        "base_url": {
          "type": "string",
          "format": "uri",
          "pattern": "^https?://",
          "description": "Base URL up to and including the version segment (e.g. .../v1)."
        },
        "dialect": { "enum": ["openai", "anthropic"] },
        "api_key_env": {
          "type": "string",
          "pattern": "^[A-Z][A-Z0-9_]*$",
          "description": "NAME of the env var holding the marketplace's key for this provider — never the key itself; a manifest is a public document."
        },
        "images_path": {
          "type": "string",
          "pattern": "^/[a-z0-9][a-z0-9/_-]{0,62}$",
          "description": "Path of the image endpoint under base_url. Absent = /images/generations (OpenAI's); OpenRouter's is /images."
        },
        "videos_path": {
          "type": "string",
          "pattern": "^/[a-z0-9][a-z0-9/_-]{0,62}$",
          "description": "Path of the video endpoint under base_url. Absent = /videos. POST creates a job, GET {path}/{id} reads it, GET {path}/{id}/content streams the file."
        },
        "decisions_path": {
          "type": "string",
          "pattern": "^/[a-z0-9][a-z0-9/_-]{0,62}$",
          "description": "Path of the decisions endpoint under base_url. Required when the manifest lists a decisions model that declares no decisions_path of its own; there is no default. TypeSafe's is /systemone, Bespoke Labs' /nimble/systemone and RouterPlus's /decisions under its Modal /v1 base (the same System One wire; one path for every model the endpoint serves), OpenRouter's /decisions (under its /api/alpha base), Fastino's /chat/completions (its GLiNER wire; the listing is still served by POST /v1/decisions only). Cloudflare Workers AI declares none: each Clef model names its own path (D33)."
        },
        "response_envelope": {
          "enum": ["cloudflare_v4"],
          "description": "Decisions endpoints only (D33): the transport envelope every answer and error comes in, opened before the wire reads the body. cloudflare_v4 = Cloudflare's REST envelope on Workers AI: a 200 is {result: <the wire's body>, success: true, errors: [], messages: []}, an error {errors: [{code, message}], success: false, result: {}}. Absent = none. Only a manifest of decisions models may declare it."
        },
        "billing": {
          "enum": ["token", "reported_cost"],
          "description": "Absent = token. reported_cost: the provider reports its own cost in USD, and every listing is priced in cost units — pricing exactly prompt \"0\" and completion \"1\", one unit per micro-dollar — so the charge is exactly that cost. Every listing then needs a price_card. Video listings need reported_cost."
        }
      }
    },
    "settlement": {
      "type": "object",
      "required": ["mode", "interval"],
      "description": "D11: how this provider gets paid.",
      "properties": {
        "mode": { "enum": ["prepaid", "arrears"] },
        "interval": { "enum": ["half_daily", "daily", "weekly", "monthly"] },
        "billing_contact": { "type": "string", "format": "email" }
      }
    },
    "models": {
      "type": "array",
      "minItems": 1,
      "items": { "$ref": "#/$defs/model" }
    }
  },
  "allOf": [
    {
      "if": { "properties": { "endpoint": { "properties": { "billing": { "const": "reported_cost" } }, "required": ["billing"] } }, "required": ["endpoint"] },
      "then": { "properties": { "models": { "items": { "required": ["price_card"] } } } }
    },
    {
      "if": { "properties": { "models": { "contains": { "properties": { "output_modality": { "const": "video" } }, "required": ["output_modality"] } } }, "required": ["models"] },
      "then": { "properties": { "endpoint": { "required": ["billing"], "properties": { "billing": { "const": "reported_cost" }, "dialect": { "const": "openai" } } } } }
    },
    {
      "if": { "properties": { "models": { "contains": { "properties": { "output_modality": { "const": "decisions" } }, "required": ["output_modality"] } } }, "required": ["models"] },
      "then": { "properties": { "endpoint": { "properties": { "billing": { "const": "token" }, "dialect": { "const": "openai" } } } } }
    },
    {
      "if": { "properties": { "models": { "contains": { "properties": { "output_modality": { "const": "decisions" } }, "required": ["output_modality"], "not": { "required": ["decisions_path"] } } } }, "required": ["models"] },
      "then": { "properties": { "endpoint": { "required": ["decisions_path"] } } }
    },
    {
      "if": { "properties": { "endpoint": { "required": ["response_envelope"] } }, "required": ["endpoint"] },
      "then": { "properties": { "models": { "items": { "required": ["output_modality"], "properties": { "output_modality": { "const": "decisions" } } } } } }
    }
  ],
  "$defs": {
    "model": {
      "type": "object",
      "required": ["id", "pricing"],
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,127}$",
          "description": "Routed verbatim and sent upstream verbatim. ':' is reserved for marketplace variant suffixes (HANDOFF §6.1) and rejected."
        },
        "display_name": { "type": "string" },
        "output_modality": {
          "enum": ["text", "image", "video", "decisions"],
          "description": "Absent = text. An image listing is served by POST /v1/images/generations only, a video listing by POST /v1/videos only; neither needs context_length or streaming. A decisions listing (a decision model: typed answers to questions about a state, in the grammar decisions_wire names) is served by POST /v1/decisions only; it needs context_length but not streaming, and it must resolve a decisions path: its own decisions_path, else the endpoint's (D33). An image listing declares max_output_tokens (the per-image ceiling), n, and a size or aspect_ratio enum. A video listing declares max_output_tokens (the ceiling per SECOND of video) and seconds as an integer range with a default."
        },
        "decisions_wire": {
          "enum": ["systemone", "gliner"],
          "description": "Decisions listings only (D28): the wire the endpoint's decisions path speaks, which fixes the question grammar buyers send. Absent = systemone (TypeSafe's Jev; the same grammar on Mercury Decide, Levanto's Sage, Bespoke Nimble v3 and Cloudflare's Clef); gliner = Fastino's /chat/completions with a GLiNER schema (D29)."
        },
        "decisions_path": {
          "type": "string",
          "pattern": "^/[a-z0-9][a-z0-9/_-]{0,62}$",
          "description": "Decisions listings only (D33): this model's own path under base_url, for a provider that picks the model by URL (Cloudflare Workers AI: /clef and /clef-flash under .../ai/run/@cf/cloudflare). Absent = the endpoint's decisions_path. The request body still carries upstream_id (or id) as model."
        },
        "decisions_state_per_question": {
          "type": "boolean",
          "description": "System One decisions listings only (D36): the provider runs one prompt per question, each with the whole state, and bills the state once per question (Perplexity's decider). The gateway then reserves the state once per question. Absent = the state counts once."
        },
        "decisions_image_parts": {
          "type": "boolean",
          "description": "System One decisions listings only (D36): the provider reads an object whose type is \"image_url\", at any depth of the state or of a question, as an image (Perplexity's decider). A decisions listing takes text, so the gateway refuses such a body with a 400 before any money is reserved. Absent = the provider reads the body as text and JSON only."
        },
        "input_modalities": {
          "type": "array",
          "items": { "enum": ["text", "image", "file", "audio", "video"] },
          "uniqueItems": true,
          "contains": { "const": "text" },
          "description": "Text listings only: what a chat message may carry, as the provider (the aggregator, for an aggregator listing) publishes it, \"text\" included. Absent = text only. The gateway sends a part only to a listing that declares its kind, and refuses a part that no listing of the model declares with a typed 400. A declared part goes as sent on a same-dialect route; a translated route refuses it with a typed 400. An image, video or decisions listing takes a text prompt and never declares it."
        },
        "context_length": { "type": "integer", "exclusiveMinimum": 0 },
        "max_output_tokens": {
          "type": "integer", "exclusiveMinimum": 0,
          "description": "Text: the most output tokens. Image: the per-image ceiling, held n times. Video: the ceiling per second, held seconds times. Under reported_cost billing image and video ceilings are in cost units (micro-dollars)."
        },
        "price_card": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/priceCardLine" },
          "description": "The prices the provider lists, for buyers to read. Display only; required under reported_cost billing."
        },
        "quantization": { "type": "string" },
        "resolves_to": {
          "type": "array",
          "items": { "type": "string", "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,127}$" },
          "description": "Other ids this model may legitimately report in response.model. A dated snapshot of the same id (-YYYYMMDD / -YYYY-MM-DD) is accepted automatically; anything else undeclared fails conformance C7 (§7.3 no silent substitution)."
        },
        "streaming": { "type": "boolean", "description": "Text listings must declare true; ignored for image and video listings." },
        "supported_parameters": {
          "type": "object",
          "additionalProperties": { "$ref": "#/$defs/descriptor" }
        },
        "pricing": {
          "type": "array",
          "minItems": 2,
          "items": { "$ref": "#/$defs/pricingEntry" },
          "description": "Must include 'prompt' and 'completion'. Omit unbilled SKUs — an ABSENT SKU defaults to its base rate (cached/cache_write -> prompt, internal_reasoning -> completion), never to $0. Declare \"0\" only for a genuinely free SKU."
        },
        "capacity": {
          "type": "array",
          "items": { "$ref": "#/$defs/capacityEntry" },
          "description": "Declared rate limits. Absent = undeclared, never zero. Identity is type+window; duplicates rejected."
        },
        "datacenters": {
          "type": "array",
          "items": { "type": "string", "pattern": "^[A-Z]{2}$" },
          "description": "ISO 3166-1 alpha-2 countries where inference may run."
        },
        "upstream_id": {
          "type": "string",
          "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,127}(?::[a-z0-9][a-z0-9-]{0,31})?$",
          "description": "The id THIS provider's endpoint expects when it differs from the marketplace id (how one catalog model is served by several providers). It may end in one provider variant suffix such as ':free' (the marketplace id itself never carries ':'). Requests to this provider carry upstream_id; C7 expects it echoed."
        },
        "is_ready": {
          "type": "boolean",
          "description": "false = staged: validated and conformance-tested, never routed and never gating the provider verdict. Absent = true."
        },
        "deprecation_date": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
          "description": "Past-dated models leave the compiled catalog at the next build (§8.10)."
        },
        "released": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
          "description": "The day the model came out: the lab's release, else the day it first listed publicly, else the day this catalog added it. Orders the Models page and the playground newest first; routing never reads it."
        }
      },
      "allOf": [
        {
          "if": { "properties": { "output_modality": { "const": "image" } }, "required": ["output_modality"] },
          "then": {
            "required": ["max_output_tokens", "supported_parameters"],
            "properties": {
              "supported_parameters": {
                "required": ["n"],
                "anyOf": [{ "required": ["size"] }, { "required": ["aspect_ratio"] }],
                "properties": {
                  "size": { "$ref": "#/$defs/enumDescriptor" },
                  "aspect_ratio": { "$ref": "#/$defs/enumDescriptor" },
                  "quality": { "$ref": "#/$defs/enumDescriptor" },
                  "resolution": { "$ref": "#/$defs/enumDescriptor" }
                }
              }
            }
          }
        },
        {
          "if": { "properties": { "output_modality": { "const": "video" } }, "required": ["output_modality"] },
          "then": {
            "required": ["max_output_tokens", "supported_parameters"],
            "properties": {
              "supported_parameters": {
                "required": ["seconds"],
                "properties": {
                  "seconds": {
                    "type": "object",
                    "required": ["type", "min", "max", "default"],
                    "properties": {
                      "type": { "const": "integer" },
                      "min": { "type": "integer", "minimum": 1 },
                      "max": { "type": "integer", "maximum": 120 },
                      "default": { "type": "integer" }
                    }
                  },
                  "resolution": { "$ref": "#/$defs/enumDescriptor" },
                  "aspect_ratio": { "$ref": "#/$defs/enumDescriptor" },
                  "size": { "$ref": "#/$defs/enumDescriptor" },
                  "generate_audio": { "type": "object", "properties": { "type": { "const": "boolean" } } }
                }
              }
            }
          }
        },
        {
          "if": { "properties": { "output_modality": { "const": "decisions" } }, "required": ["output_modality"] },
          "then": { "required": ["context_length"] }
        },
        {
          "if": { "required": ["decisions_wire"] },
          "then": { "required": ["output_modality"], "properties": { "output_modality": { "const": "decisions" } } }
        },
        {
          "if": { "required": ["decisions_path"] },
          "then": { "required": ["output_modality"], "properties": { "output_modality": { "const": "decisions" } } }
        },
        {
          "if": { "anyOf": [{ "required": ["decisions_state_per_question"] }, { "required": ["decisions_image_parts"] }] },
          "then": {
            "required": ["output_modality"],
            "properties": { "output_modality": { "const": "decisions" }, "decisions_wire": { "const": "systemone" } }
          }
        },
        {
          "if": { "properties": { "output_modality": { "enum": ["image", "video", "decisions"] } }, "required": ["output_modality"] },
          "then": { "not": { "required": ["input_modalities"] } },
          "else": {
            "required": ["context_length", "streaming"],
            "properties": { "streaming": { "const": true } }
          }
        }
      ]
    },
    "enumDescriptor": {
      "type": "object",
      "required": ["type", "values"],
      "properties": { "type": { "const": "enum" }, "values": { "type": "array", "minItems": 1 } }
    },
    "priceCardLine": {
      "type": "object",
      "required": ["unit", "cost_usd"],
      "additionalProperties": false,
      "properties": {
        "unit": { "enum": ["token", "image", "second", "megapixel"] },
        "cost_usd": { "type": "string", "pattern": "^\\d{1,6}(\\.\\d{1,9})?$", "description": "Decimal USD per unit, e.g. \"0.045\" per image or \"0.0000107\" per token." },
        "billable": { "type": "string", "minLength": 1, "maxLength": 64, "description": "What the line bills, e.g. output_image, input_image or output_video." },
        "variant": { "type": "string", "minLength": 1, "maxLength": 64, "description": "The tier it applies to, e.g. 1080p, high_resolution or medium_2k." }
      }
    },
    "pricingEntry": {
      "type": "object",
      "required": ["type", "unit", "cost_usd_per_million"],
      "properties": {
        "type": { "enum": ["prompt", "cached_prompt", "cache_write", "completion", "internal_reasoning"] },
        "unit": { "const": "token" },
        "cost_usd_per_million": {
          "type": "string",
          "pattern": "^\\d{1,7}(\\.\\d{1,6})?$",
          "description": "Decimal STRING, USD per million tokens (e.g. \"2.50\") — never a float; compiled to integer micro-USD by exact string math."
        }
      }
    },
    "capacityEntry": {
      "type": "object",
      "required": ["type", "per", "value"],
      "properties": {
        "type": { "enum": ["request", "prompt", "completion"] },
        "unit": { "const": "token", "description": "Required for token-denominated types (prompt/completion)." },
        "per": { "enum": ["minute", "hour", "day"] },
        "value": { "type": "integer", "exclusiveMinimum": 0 }
      }
    },
    "descriptor": {
      "type": "object",
      "required": ["type"],
      "description": "Capability descriptor: absent key = unsupported; 'boolean' presence = supported.",
      "oneOf": [
        { "properties": { "type": { "const": "boolean" } } },
        {
          "properties": {
            "type": { "const": "range" },
            "min": { "type": "number" },
            "max": { "type": "number" },
            "default": { "type": "number" }
          },
          "required": ["type", "min", "max"]
        },
        {
          "properties": {
            "type": { "const": "integer" },
            "min": { "type": "integer" },
            "max": { "type": "integer" },
            "unit": { "type": "string" },
            "default": { "type": "integer" }
          }
        },
        {
          "properties": {
            "type": { "const": "enum" },
            "values": { "type": "array", "minItems": 1, "items": { "type": "string" } },
            "default": { "type": "string" }
          },
          "required": ["type", "values"]
        },
        { "properties": { "type": { "const": "unknown" } } }
      ]
    }
  }
}
