{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/audivo-dev/json-schema/audivo-dev-quote-response-schema.json",
  "title": "QuoteResponse",
  "description": "The recorded quote. `total_ceiling_credits` is the sum of the entries' ceilings and `cached_members` the count of entries with `is_cached: true` — both derived by the store at write time, never taken from a caller. The quote is valid until `expires_at`; a confirm after that is refused.\n\n`balance_credits` and `reserved_credits` are the caller's account as it stood when the quote was priced, read off the same row `remaining_open_jobs` was counted from — so an agent can see what it holds at the moment it decides to spend without a second call. Like the headroom beside them they are **reported, never enforced**: a quote reserves nothing, so a balance below `total_ceiling_credits` is a fact to act on rather than a refusal, and affordability is decided by the confirm.",
  "x-generated": "2026-10-30",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/audivo-dev-openapi.yaml#/components/schemas/QuoteResponse",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "quote_id",
    "source",
    "episodes_per_show",
    "entries",
    "excluded",
    "clamps",
    "cached_members",
    "uncached_members",
    "total_ceiling_credits",
    "remaining_open_jobs",
    "balance_credits",
    "reserved_credits",
    "expires_at",
    "created_at"
  ],
  "properties": {
    "quote_id": {
      "$ref": "#/$defs/QuoteId"
    },
    "source": {
      "type": "string",
      "enum": [
        "shows",
        "chart",
        "uploads"
      ],
      "description": "Which request shape produced this quote."
    },
    "episodes_per_show": {
      "type": "integer",
      "minimum": 1,
      "description": "The N after the tier had its say."
    },
    "entries": {
      "type": "array",
      "minItems": 1,
      "maxItems": 100,
      "items": {
        "$ref": "#/$defs/QuoteEntry"
      }
    },
    "excluded": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/QuoteExclusion"
      }
    },
    "clamps": {
      "type": "array",
      "maxItems": 3,
      "items": {
        "$ref": "#/$defs/SelectionClamp"
      }
    },
    "cached_members": {
      "type": "integer",
      "minimum": 0
    },
    "uncached_members": {
      "type": "integer",
      "minimum": 0
    },
    "total_ceiling_credits": {
      "$ref": "#/$defs/CreditAmount"
    },
    "remaining_open_jobs": {
      "type": "integer",
      "minimum": 0,
      "description": "How many more jobs the caller's tier lets them have open — reserved and not yet terminal — right now, so a selection can be sized before it is confirmed. Reported here, enforced at confirm."
    },
    "balance_credits": {
      "$ref": "#/$defs/CreditAmount"
    },
    "reserved_credits": {
      "$ref": "#/$defs/CreditAmount"
    },
    "expires_at": {
      "type": "string",
      "format": "date-time"
    },
    "created_at": {
      "type": "string",
      "format": "date-time"
    }
  },
  "$defs": {
    "CreditAmount": {
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991,
      "description": "A non-negative JavaScript-safe integer credit amount. The upper bound matches `Number.MAX_SAFE_INTEGER`, which is enforced by the domain ledger so arithmetic and JSON round-trips cannot silently lose cents worth of credit precision."
    },
    "EpisodeGuid": {
      "type": "string",
      "minLength": 1,
      "maxLength": 512,
      "description": "An episode GUID as published in the feed's `<guid>` element. Used verbatim — GUIDs are case- and whitespace-significant."
    },
    "EpisodeId": {
      "type": "string",
      "pattern": "^ep_[a-z2-7]{16}$",
      "description": "Canonical episode identifier."
    },
    "FeedUrl": {
      "type": "string",
      "format": "uri",
      "minLength": 1,
      "maxLength": 2048,
      "description": "An RSS/Atom feed URL."
    },
    "NullableDateTime": {
      "description": "An ISO 8601 timestamp, or `null`.",
      "anyOf": [
        {
          "type": "string",
          "format": "date-time"
        },
        {
          "enum": [
            null
          ]
        }
      ]
    },
    "QuoteBasis": {
      "type": "string",
      "enum": [
        "feed_metadata",
        "probed",
        "declared"
      ],
      "description": "`feed_metadata` when the estimate is derived from feed-published duration; `probed` when a bounded enclosure probe measured it. `declared` when the caller stated it for an upload (ADR-0031)."
    },
    "QuoteEntry": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "episode_id",
        "show_id",
        "feed_url",
        "guid",
        "show_title",
        "episode_title",
        "published_at",
        "is_cached",
        "estimated_credits",
        "quote_ceiling_credits",
        "quote_basis",
        "declared_duration_seconds"
      ],
      "description": "One priced episode. An uncached entry carries the domain's estimate from the feed-declared duration and its ceiling; a cached one is priced at what a cached read costs the calling account with no ceiling above it — there is no measurement left to exceed.",
      "properties": {
        "episode_id": {
          "$ref": "#/$defs/EpisodeId"
        },
        "show_id": {
          "$ref": "#/$defs/ShowId"
        },
        "feed_url": {
          "$ref": "#/$defs/FeedUrl"
        },
        "guid": {
          "$ref": "#/$defs/EpisodeGuid"
        },
        "show_title": {
          "type": "string",
          "minLength": 1,
          "maxLength": 300
        },
        "episode_title": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 300
            },
            {
              "enum": [
                null
              ]
            }
          ]
        },
        "published_at": {
          "$ref": "#/$defs/NullableDateTime"
        },
        "is_cached": {
          "type": "boolean"
        },
        "estimated_credits": {
          "$ref": "#/$defs/CreditAmount"
        },
        "quote_ceiling_credits": {
          "$ref": "#/$defs/CreditAmount"
        },
        "quote_basis": {
          "$ref": "#/$defs/QuoteBasis"
        },
        "declared_duration_seconds": {
          "type": "number",
          "minimum": 0,
          "description": "The feed's `<itunes:duration>`, or the caller's declaration for an upload, in seconds — what the estimate is priced from."
        },
        "upload_id": {
          "$ref": "#/$defs/UploadId",
          "description": "Present when the entry is an upload; `feed_url` is then the account's private `audivo://uploads/…` show."
        }
      }
    },
    "QuoteExclusion": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "feed_url",
        "guid",
        "title",
        "reason",
        "detail"
      ],
      "description": "One thing the caller asked for that is not in `entries`. `guid` is `null` when a whole show was excluded; `feed_url` is `null` only when the provider carried none. `title` is what a surface shows a human — the show's name, or `show — episode` for one episode.",
      "properties": {
        "feed_url": {
          "anyOf": [
            {
              "$ref": "#/$defs/FeedUrl"
            },
            {
              "enum": [
                null
              ]
            }
          ]
        },
        "guid": {
          "anyOf": [
            {
              "$ref": "#/$defs/EpisodeGuid"
            },
            {
              "enum": [
                null
              ]
            }
          ]
        },
        "title": {
          "type": "string",
          "minLength": 1,
          "maxLength": 600
        },
        "reason": {
          "$ref": "#/$defs/QuoteExclusionReason"
        },
        "detail": {
          "type": "string",
          "minLength": 1,
          "maxLength": 1000
        }
      }
    },
    "QuoteExclusionReason": {
      "type": "string",
      "enum": [
        "feed_unavailable",
        "feed_unparseable",
        "budget_exceeded",
        "music_led",
        "no_feed_url",
        "duplicate_show",
        "chart_size_capped",
        "total_selection_capped",
        "no_episodes",
        "no_guid",
        "no_enclosure",
        "episode_not_in_feed",
        "episode_not_found",
        "no_declared_duration",
        "no_stable_asset_revision",
        "upload_not_found",
        "upload_not_received",
        "upload_mismatch"
      ],
      "description": "Every reason a show or an episode can be missing from a quote, in one closed vocabulary shared by the selection rule and the quote: the caller reads one list, not two. The `nothing_to_quote` error message counts these same words."
    },
    "QuoteId": {
      "type": "string",
      "pattern": "^qte_[A-Za-z0-9]{16,32}$",
      "maxLength": 40,
      "description": "Opaque, server-generated quote identifier."
    },
    "SelectionClamp": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "dimension",
        "requested",
        "allowed",
        "limit",
        "clamped",
        "detail"
      ],
      "description": "A tier ceiling that bit, in the terms the caller used.",
      "properties": {
        "dimension": {
          "type": "string",
          "enum": [
            "chart_size",
            "episodes_per_show",
            "total_selection"
          ]
        },
        "requested": {
          "type": "integer",
          "minimum": 1
        },
        "allowed": {
          "type": "integer",
          "minimum": 0
        },
        "limit": {
          "type": "integer",
          "minimum": 1
        },
        "clamped": {
          "const": true
        },
        "detail": {
          "type": "string",
          "minLength": 1,
          "maxLength": 1000
        }
      }
    },
    "ShowId": {
      "type": "string",
      "pattern": "^sh_[a-z2-7]{16}$",
      "description": "Canonical show identifier."
    },
    "UploadId": {
      "type": "string",
      "pattern": "^upl_[A-Za-z0-9]{16,32}$",
      "maxLength": 40,
      "description": "Opaque, server-generated upload identifier."
    }
  }
}