{
  "$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", "maxLength": 2048, "pattern": "^https?://[^\\s\\u0000-\\u001f\\u007f-\\u009f]*$", "description": "D65 am. 1: http(s), at most 2,048 characters, no whitespace or control characters. A self-serve provider may write it: a value other than the one the state it replaces shows is pushed when it differs from this file's value at write time (it follows this file when it equals it); a PATCH that leaves it out, and any write that sends the value shown, keeps the stored form of the state it replaces (a pushed URL stays pushed); a POST that leaves it out sets this file's value; null goes back to this file." },
        "terms_of_service_url": { "type": "string", "format": "uri", "maxLength": 2048, "pattern": "^https?://[^\\s\\u0000-\\u001f\\u007f-\\u009f]*$", "description": "D65 am. 1: http(s), at most 2,048 characters, no whitespace or control characters. A self-serve provider may write it: a value other than the one the state it replaces shows is pushed when it differs from this file's value at write time (it follows this file when it equals it); a PATCH that leaves it out, and any write that sends the value shown, keeps the stored form of the state it replaces (a pushed URL stays pushed); a POST that leaves it out sets this file's value; null goes back to this file." },
        "status_page_url": { "type": "string", "format": "uri", "maxLength": 2048, "pattern": "^https?://[^\\s\\u0000-\\u001f\\u007f-\\u009f]*$", "description": "D65 am. 1: http(s), at most 2,048 characters, no whitespace or control characters. A self-serve provider may write it: a value other than the one the state it replaces shows is pushed when it differs from this file's value at write time (it follows this file when it equals it); a PATCH that leaves it out, and any write that sends the value shown, keeps the stored form of the state it replaces (a pushed URL stays pushed); a POST that leaves it out sets this file's value; null goes back to this file." },
        "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\"."
        },
        "self_serve": {
          "type": "boolean",
          "description": "D65: true = this listing's editable fields (the policy URLs; per model pricing, provider_pricing, enabled, and context_length and max_output_tokens, lowered only; not a decisions model's limits, nor an image or video model's max_output_tokens, which are the operator's) are live: the provider writes them with a provider key (GET, POST or PATCH /v1/provider/manifest), RouterPlus with the provider-admin token, or endpoint.price_feed copies the provider's published prices. Its buyer price is its own, outside the identical-price rule: before any revision it is this file's pricing. Not with listing \"private\" or billing \"reported_cost\". Operator-only: a push can never set or clear it. Absent = false."
        },
        "routing": {
          "enum": ["default", "opt_in"],
          "description": "D70: \"opt_in\" = on a model id another public provider also lists, this provider is routed only when the request names it (provider.only or provider.order, or a routing policy's house deployment), and that request is charged this listing's own pricing; every other request never reaches it and pays the model's one list price. On a model only it serves it is an ordinary listing whose pricing is the list price. With self_serve (D65), the listing's own price is the revision in force, else this file's pricing. Not with listing \"private\". Absent = \"default\"."
        }
      }
    },
    "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_transport": { "enum": ["krea", "recraft"], "description": "krea: native async Krea image jobs. recraft: Recraft's synchronous generations, credits read as the reported cost (D69)." },
        "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)."
        },
        "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), 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."
        },
        "decisions_format": {
          "enum": ["openai"],
          "description": "Decisions endpoints only (D38): the body format the provider speaks when it is not System One's. openai = OpenAI's POST /v1/decisions: input and an ordered questions array of predicate, choice and score questions, answered by an ordered answers array. The buyer still writes System One; the gateway translates the request to this format and the answers back to System One's. Absent = the wire's own body. Only a manifest of System One 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."
        },
        "price_feed": {
          "type": "object",
          "required": ["format"],
          "additionalProperties": false,
          "description": "D65 amendment 1: where control reads this provider's published prices on a schedule, for a self-serve listing only. A decrease becomes a revision now, an increase a revision after the notice period. While it is set, a provider write may not change prices (it may change the other editable fields, and such a write composes with a pending fed increase). With format openai_models no model may declare provider_pricing (the feed publishes no provider cost, so the listing is pass-through), and no write may set one; a cost a revision carries from before the feed was set makes the feed refuse until an operator clears it. The feed is held to the one-tenth price floor, like a provider key. Operator-only, like all of endpoint.",
          "properties": {
            "format": {
              "enum": ["openai_models", "routerplus"],
              "description": "openai_models: an OpenAI-compatible model list, {data: [{id, pricing: {prompt, completion, input_cache_read?, input_cache_write?, internal_reasoning?}}]}, USD per token as decimal strings, matched by upstream_id else id; the listing is then pass-through. routerplus: {models: [{id, pricing, provider_pricing?}]} in this manifest's price form, keyed by the marketplace id."
            },
            "url": { "type": "string", "format": "uri", "pattern": "^https://", "description": "https only. Absent = base_url + \"/models\" (openai_models only)." },
            "auth": { "enum": ["api_key", "none"], "description": "api_key = send our key for this provider (api_key_env) as a bearer token. Absent = none." }
          }
        }
      }
    },
    "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": [
    {
      "$comment": "D65 amendment 1: a self-serve listing is public and token-billed.",
      "if": { "properties": { "provider": { "properties": { "self_serve": { "const": true } }, "required": ["self_serve"] } }, "required": ["provider"] },
      "then": { "properties": { "provider": { "properties": { "listing": { "const": "public" } } }, "endpoint": { "properties": { "billing": { "const": "token" } } } } }
    },
    {
      "$comment": "D65 amendment 1: a price feed needs a self-serve listing; routerplus names its url; api_key auth needs api_key_env.",
      "if": { "properties": { "endpoint": { "required": ["price_feed"] } }, "required": ["endpoint"] },
      "then": {
        "properties": {
          "provider": { "required": ["self_serve"], "properties": { "self_serve": { "const": true } } },
          "endpoint": {
            "allOf": [
              { "if": { "properties": { "price_feed": { "properties": { "format": { "const": "routerplus" } } } } }, "then": { "properties": { "price_feed": { "required": ["url"] } } } },
              { "if": { "properties": { "price_feed": { "properties": { "auth": { "const": "api_key" } }, "required": ["auth"] } } }, "then": { "required": ["api_key_env"] } }
            ]
          }
        }
      }
    },
    {
      "$comment": "D60: streaming false (the gateway streams a buffered answer) and image_as_document are an OpenAI chat completion's only.",
      "if": { "properties": { "endpoint": { "properties": { "dialect": { "const": "anthropic" } }, "required": ["dialect"] } }, "required": ["endpoint"] },
      "then": {
        "properties": {
          "models": {
            "items": {
              "if": { "not": { "properties": { "output_modality": { "enum": ["image", "video", "decisions"] } }, "required": ["output_modality"] } },
              "then": { "properties": { "streaming": { "const": true } } },
              "not": { "properties": { "image_as_document": { "const": true } }, "required": ["image_as_document"] }
            }
          }
        }
      }
    },
    {
      "$comment": "D71: thinking_off (chat_template_kwargs) is an OpenAI chat completion's only.",
      "if": { "properties": { "endpoint": { "properties": { "dialect": { "const": "anthropic" } }, "required": ["dialect"] } }, "required": ["endpoint"] },
      "then": { "properties": { "models": { "items": { "not": { "properties": { "thinking_off": { "const": true } }, "required": ["thinking_off"] } } } } }
    },
    {
      "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" } } } } } }
    },
    {
      "if": { "properties": { "endpoint": { "required": ["decisions_format"] } }, "required": ["endpoint"] },
      "then": { "properties": { "models": { "items": { "required": ["output_modality"], "properties": { "output_modality": { "const": "decisions" }, "decisions_wire": { "const": "systemone" } } } } } }
    }
  ],
  "$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 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."
        },
        "decisions_text_only": {
          "type": "boolean",
          "description": "System One decisions listings only (D60): the provider reads System One as text only (ScaleDown's classify-1): no null option descriptions, no JSON structures as descriptions, levels or instructions, and a state that is a string or {text, document}. The gateway sends a null description as \"\", a structure as its JSON text, and any other state as {\"text\": its JSON text}. Absent = the provider reads System One's JSON as Jev does."
        },
        "decisions_one_option_billed": {
          "type": "boolean",
          "description": "System One decisions listings with decisions_state_per_question only (D60): the provider also runs, and bills the state for, a one-option choice (ScaleDown's classify-1). The gateway then holds the state for every question. Absent = a one-option choice runs no prompt and takes no copy of the state (Perplexity's decider, D36)."
        },
        "image_as_document": {
          "type": "boolean",
          "description": "Text listings on an OpenAI-dialect endpoint only (D60): the provider takes an image or file as a top-level document (base64) with document_mime_type, not as a message part (ScaleDown's extract-1 and summarize-1). The gateway moves the one image or file part of the last user message there and sends that message's text as a plain string; a web URL, a second attachment or an image with no text is a 400. Requires input_modalities with image or file."
        },
        "inline_images_only": {
          "type": "boolean",
          "description": "Text listings with image input only (D83): true = the provider takes an image only inline (a base64 data URL, or an Anthropic base64 source) and refuses one given by link with a 400 (RunInfra). A request that carries an image link goes to another route of the model that takes links when one can serve it; when none can, this route answers for itself. Absent = the provider takes links too."
        },
        "max_images": {
          "type": "integer",
          "minimum": 1,
          "maximum": 10000,
          "description": "Text listings with image input only (D83 decision 16): the most images the provider takes in one request, counted over every message (RunInfra: 8; a ninth is its 400, which never fails over). The gateway never sends this route a request with more; when no other route can take it, the request is refused with a 400 that names the limit. Absent = no limit."
        },
        "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. D60: a decisions listing may also declare it, for what its state can carry (ScaleDown's classify-1 reads a state.document image or PDF by OCR); it is shown on the catalog and changes nothing in routing."
        },
        "chat_api": {
          "enum": ["responses"],
          "description": "Text listings on an OpenAI-dialect endpoint only (D41): the gateway calls this model on the provider's Responses API (POST /responses under base_url) instead of Chat Completions, because the model takes function tools only there (OpenAI's GPT-6 and GPT-5.6). Buyers still use either chat surface; the gateway translates the request and the answer. Absent = Chat Completions."
        },
        "fixed_sampling": {
          "type": "boolean",
          "description": "Text listings only (D41): true = the model accepts only its default sampling, and the provider refuses temperature (other than 1), top_p and top_k with a 400 (Anthropic's Claude 5 models, OpenAI's GPT-6 and GPT-5.6). The gateway leaves them out on every route of the model and records them as dropped. Absent = the provider takes them."
        },
        "thinking_off": {
          "type": "boolean",
          "description": "Text listings on an OpenAI-dialect endpoint only (D71): true = this provider serves the model with its thinking turned off, because its thinking ignores max_tokens and is billed as output (Qwen3.7 Max and Qwen3.8 Max and Flash on DeepInfra and Novita). The gateway sends chat_template_kwargs {enable_thinking: false} on this route and never the buyer's reasoning_effort (recorded as dropped unless it asked for none). Absent = the provider's own default."
        },
        "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 it: true = the provider streams SSE; false (D60, OpenAI dialect only) = it answers in one JSON body and the gateway streams the finished answer to a stream buyer. Ignored for image, video and decisions listings." },
        "required_parameters": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
        "request_defaults": { "type": "object" },
        "krea_pricing": { "$ref": "#/$defs/kreaPricing" },
        "supported_parameters": {
          "type": "object",
          "properties": {
            "response_format": {
              "type": "object",
              "required": ["type", "values"],
              "properties": {
                "type": { "const": "enum" },
                "values": {
                  "type": "array",
                  "uniqueItems": true,
                  "items": { "enum": ["text", "json_object", "json_schema"] },
                  "contains": { "const": "text" }
                }
              },
              "description": "Text listings: the response_format types this route honours. A request asking for a type left out (json_object, json_schema) skips the route; when no route of the model takes it, the gateway answers 400. Absent = no constraint."
            }
          },
          "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."
        },
        "provider_pricing": {
          "type": "array",
          "minItems": 2,
          "items": { "$ref": "#/$defs/pricingEntry" },
          "description": "What this provider charges the marketplace for the model, when it is not `pricing` (D11.4). A model several providers serve has one buyer price; a provider whose own rate differs declares it here, and is owed its usage at this rate. Same form and rules as `pricing`; token billing only. Absent = the provider charges `pricing`."
        },
        "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."
        },
        "enabled": {
          "type": "boolean",
          "description": "D65 amendment 1 (phase 2): false = the provider has turned this model off: no house (marketplace) route reaches it and the public lists hide it, until a later revision turns it on again; a buyer's own-key (BYOK) or policy BYOK route is unaffected, and a freeze does not undo it (turning self_serve off does: then the gateway ignores every revision, prices and model states). Set by a provider write; a repo file stages a model with is_ready instead (the catalog build treats enabled false in a repo file like is_ready false). Absent = true."
        },
        "listed": {
          "type": "boolean",
          "description": "false = accepted by name only (D64): routed, priced and billed like any model, but no list shows it (GET /v1/models, the Models page, /api/models.json, the Playground). For a provider's moving name, such as \"nimble-latest\", that the provider resolves itself. 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"],
            "anyOf": [{ "required": ["krea_pricing"] }, { "properties": { "supported_parameters": { "anyOf": [{ "required": ["size"] }, { "required": ["aspect_ratio"] }] } } }],
            "properties": {
              "supported_parameters": {
                "required": ["n"],
                "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" },
                      "values": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "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"] }, { "required": ["decisions_text_only"] }, { "required": ["decisions_one_option_billed"] }] },
          "then": {
            "required": ["output_modality"],
            "properties": { "output_modality": { "const": "decisions" }, "decisions_wire": { "const": "systemone" } }
          }
        },
        {
          "$comment": "D60: image_as_document is a text listing's, and only with image or file input.",
          "if": { "properties": { "image_as_document": { "const": true } }, "required": ["image_as_document"] },
          "then": {
            "required": ["input_modalities"],
            "properties": {
              "output_modality": { "const": "text" },
              "input_modalities": { "anyOf": [{ "contains": { "const": "image" } }, { "contains": { "const": "file" } }] }
            }
          }
        },
        {
          "$comment": "D83: inline_images_only is a text listing's, and only with image input.",
          "if": { "properties": { "inline_images_only": { "const": true } }, "required": ["inline_images_only"] },
          "then": {
            "required": ["input_modalities"],
            "properties": { "output_modality": { "const": "text" }, "input_modalities": { "contains": { "const": "image" } } }
          }
        },
        {
          "$comment": "D83 decision 16: max_images is a text listing's, and only with image input.",
          "if": { "required": ["max_images"] },
          "then": {
            "required": ["input_modalities"],
            "properties": { "output_modality": { "const": "text" }, "input_modalities": { "contains": { "const": "image" } } }
          }
        },
        {
          "$comment": "D60: a billed one-option choice changes the per-question hold only.",
          "if": { "properties": { "decisions_one_option_billed": { "const": true } }, "required": ["decisions_one_option_billed"] },
          "then": { "required": ["decisions_state_per_question"], "properties": { "decisions_state_per_question": { "const": true } } }
        },
        {
          "$comment": "D60: a buffered listing (streaming false) and the Responses API take the same converter slot.",
          "if": { "properties": { "streaming": { "const": false } }, "required": ["streaming"] },
          "then": { "not": { "required": ["chat_api"] } }
        },
        {
          "if": { "anyOf": [{ "required": ["chat_api"] }, { "required": ["fixed_sampling"] }, { "required": ["thinking_off"] }] },
          "then": { "not": { "properties": { "output_modality": { "enum": ["image", "video", "decisions"] } }, "required": ["output_modality"] } }
        },
        {
          "if": { "properties": { "output_modality": { "enum": ["image", "video", "decisions"] } }, "required": ["output_modality"] },
          "then": {
            "$comment": "D60: a decisions listing may declare input_modalities (what its state can carry, e.g. a document read by OCR); image and video listings take a text prompt only.",
            "if": { "properties": { "output_modality": { "enum": ["image", "video"] } } },
            "then": { "not": { "required": ["input_modalities"] } }
          },
          "else": {
            "required": ["context_length", "streaming"],
            "properties": { "streaming": { "type": "boolean" } }
          }
        }
      ]
    },
    "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", "request"], "description": "request = a fee charged once per request, whatever n is." },
        "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",
      "additionalProperties": false,
      "description": "D65 am. 1: exactly type, unit and cost_usd_per_million; another key is refused with its path.",
      "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" },
            "values": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "integer" } }
          }
        },
        {
          "properties": {
            "type": { "const": "enum" },
            "values": { "type": "array", "minItems": 1, "items": { "type": "string" } },
            "default": { "type": "string" }
          },
          "required": ["type", "values"]
        },
        { "properties": { "type": { "const": "unknown" }, "schema": { "$ref": "#/$defs/imageParameterSchema" } } }
      ]
    },
    "imageParameterSchema": {
      "type": "object",
      "properties": {
        "type": {
          "enum": [
            "string",
            "number",
            "integer",
            "boolean",
            "array",
            "object",
            "null"
          ]
        },
        "enum": {
          "type": "array",
          "minItems": 1
        },
        "minimum": {
          "type": "number"
        },
        "maximum": {
          "type": "number"
        },
        "format": {
          "type": "string"
        },
        "pattern": {
          "type": "string",
          "maxLength": 1000
        },
        "description": {
          "type": "string"
        },
        "default": {},
        "additionalProperties": {
          "type": "boolean"
        },
        "required": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "properties": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/$defs/imageParameterSchema"
          }
        },
        "items": {
          "$ref": "#/$defs/imageParameterSchema"
        },
        "anyOf": {
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/imageParameterSchema"
          }
        },
        "minLength": {
          "type": "integer",
          "minimum": 0
        },
        "maxLength": {
          "type": "integer",
          "minimum": 0
        },
        "minItems": {
          "type": "integer",
          "minimum": 0
        },
        "maxItems": {
          "type": "integer",
          "minimum": 0
        }
      },
      "additionalProperties": false
    },
    "kreaPricing": {
      "type": "object",
      "required": [
        "price_points"
      ],
      "properties": {
        "resolution_tier": {
          "type": "object",
          "required": ["max_pixels", "lower_tier", "upper_tier"],
          "additionalProperties": false,
          "properties": {
            "max_pixels": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 },
            "lower_tier": { "type": "string", "minLength": 1, "pattern": "\\S" },
            "upper_tier": { "type": "string", "minLength": 1, "pattern": "\\S" }
          },
          "description": "Derive resolutionTier from width times height: lower_tier at or below max_pixels, upper_tier above. Labels must be distinct and match the price points."
        },
        "price_points": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "required": [
              "amount",
              "dimensions"
            ],
            "properties": {
              "amount": {
                "type": "number",
                "minimum": 0
              },
              "dimensions": {
                "type": "object",
                "additionalProperties": {
                  "type": [
                    "string",
                    "number",
                    "boolean"
                  ]
                }
              }
            }
          }
        }
      }
    }
  }
}
