{
  "openapi": "3.0.3",
  "info": {
    "title": "BRAINSALT Server Configurator API",
    "description": "Finds matching BRAINSALT media servers (B8/BX8, B9/BX9 series) for a set of video streams and requests quotes by email. No authentication, CORS enabled, no prices exposed - quotes go through the proposal action. Human-readable guide: https://www.brainsalt.com/embed/configurator-api.md",
    "version": "1.0.0",
    "contact": { "url": "https://www.brainsalt.com" }
  },
  "servers": [{ "url": "https://www.brainsalt.com" }],
  "paths": {
    "/embed/ConfiguratorApi.ashx": {
      "get": {
        "summary": "options / specs / pdf / image (selected by the action parameter)",
        "description": "action=options: valid option values and help texts. action=specs&article=NR: full specification of one server. action=pdf&article=NR: spec sheet PDF. action=image&article=NR&view=front|rear[&invert=1]: server photo or configuration-accurate rear view PNG (invert=1 for dark backgrounds).",
        "parameters": [
          { "name": "action", "in": "query", "required": true, "schema": { "type": "string", "enum": ["options", "specs", "pdf", "image"] } },
          { "name": "article", "in": "query", "required": false, "schema": { "type": "string", "pattern": "^[A-Za-z0-9.\\-]{1,64}$" }, "description": "Article number, required for specs, pdf and image." },
          { "name": "view", "in": "query", "required": false, "schema": { "type": "string", "enum": ["front", "rear"] }, "description": "Image view (action=image)." },
          { "name": "invert", "in": "query", "required": false, "schema": { "type": "string", "enum": ["1"] }, "description": "Rear view as white line art for dark backgrounds (action=image)." }
        ],
        "responses": {
          "200": { "description": "JSON (options/specs), PDF (pdf) or PNG (image)." },
          "400": { "description": "Invalid action or parameters." },
          "404": { "description": "Unknown article or no image available." },
          "429": { "description": "Rate limit exceeded (per IP and minute: options/specs/image 120, pdf 6)." }
        }
      },
      "post": {
        "summary": "match / proposal (selected by the action parameter)",
        "description": "action=match: find servers for the given stream requirements. action=proposal: request a quote by email for an article, or send a multi-server project description when Article is empty. Recipient and rebate are configured server-side; the requester's email becomes Reply-To.",
        "parameters": [
          { "name": "action", "in": "query", "required": true, "schema": { "type": "string", "enum": ["match", "proposal"] } }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  { "$ref": "#/components/schemas/MatchRequest" },
                  { "$ref": "#/components/schemas/ProposalRequest" }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "MatchResponse for action=match, {\"ok\":true} for action=proposal.",
            "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/MatchResponse" }, { "type": "object", "properties": { "ok": { "type": "boolean" } } } ] } } }
          },
          "400": { "description": "Invalid request." },
          "429": { "description": "Rate limit exceeded (per IP and minute: match 30, proposal 3)." }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "StreamSpec": {
        "type": "object",
        "description": "One kind of video the server must handle; Streams = how many run simultaneously.",
        "properties": {
          "Width": { "type": "integer", "example": 3840 },
          "Height": { "type": "integer", "example": 2160 },
          "Fps": { "type": "number", "example": 60 },
          "Streams": { "type": "integer", "example": 2 },
          "DurationMin": { "type": "number", "description": "Playback duration in minutes (content streams only, sizes the storage).", "example": 30 }
        },
        "required": ["Width", "Height", "Fps", "Streams"]
      },
      "MatchRequest": {
        "type": "object",
        "properties": {
          "Type": { "type": "string", "enum": ["player", "cms"], "description": "player = media server, cms = content management server." },
          "ContentStreams": { "type": "array", "items": { "$ref": "#/components/schemas/StreamSpec" }, "description": "Videos played from storage, all at the same time." },
          "RealtimeStreams": { "type": "array", "items": { "$ref": "#/components/schemas/StreamSpec" }, "description": "Live inputs (capture/NDI), processed in addition to content playback. No storage." },
          "Codec": { "type": "string", "enum": ["Uncompressed", "NotchLC"], "description": "NotchLC reduces storage size/bandwidth to 25%, always format U_444_10Bit." },
          "Format": { "type": "string", "description": "From options.formats, e.g. U_444_10Bit." },
          "Outputs": { "type": "integer", "description": "Physical outputs (players only). More than 4 requires genlock." },
          "OutputResolution": { "type": "string", "description": "From options.outputResolutions, e.g. _4K (players only)." },
          "Psu": { "type": "string", "enum": ["Any", "Swappable", "Redundant"] },
          "Genlock": { "type": "string", "enum": ["None", "Genlock"] },
          "AddOn1": { "type": "string", "description": "Add-on card name from options.addOnCardsB8/B9 or None." },
          "AddOn2": { "type": "string" },
          "AddOn3": { "type": "string" },
          "AddOn4": { "type": "string" }
        },
        "required": ["Type"]
      },
      "MatchResponse": {
        "type": "object",
        "properties": {
          "Error": { "type": "string", "nullable": true },
          "Issues": { "type": "array", "items": { "type": "string" }, "description": "Non-fatal adjustments, e.g. genlock forced above 4 outputs." },
          "GenlockForced": { "type": "boolean" },
          "AddOnCards34Cleared": { "type": "boolean" },
          "ExceedsSingleServerBandwidth": { "type": "boolean", "description": "True: no single server fits - such projects use multiple synchronized servers; send a project description via action=proposal with empty Article." },
          "EffectiveFormat": { "type": "string" },
          "Bandwidth": {
            "type": "object",
            "properties": {
              "StorageBandwidthMB": { "type": "integer" },
              "StorageSizeGB": { "type": "integer" },
              "InternalBandwidthMB": { "type": "integer" },
              "ContentPixelRate": { "type": "number" },
              "RealtimePixelRate": { "type": "number" }
            }
          },
          "Compact": { "type": "array", "items": { "$ref": "#/components/schemas/ServerResult" } },
          "Entry": { "type": "array", "items": { "$ref": "#/components/schemas/ServerResult" } },
          "Pro": { "type": "array", "items": { "$ref": "#/components/schemas/ServerResult" } }
        }
      },
      "ServerResult": {
        "type": "object",
        "description": "A matching server. Prices are intentionally not exposed - request a quote via action=proposal.",
        "properties": {
          "ArticleNr": { "type": "string" },
          "Title": { "type": "string" },
          "Description": { "type": "string" },
          "MaxPowerW": { "type": "integer" },
          "TypicalPowerW": { "type": "integer" },
          "WeightKg": { "type": "number" },
          "ShipSize": { "type": "string", "description": "Shipping size in mm." },
          "CountryOfOrigin": { "type": "string" },
          "CustomsTariff": { "type": "string" }
        }
      },
      "ProposalRequest": {
        "type": "object",
        "properties": {
          "Article": { "type": "string", "description": "Article number to quote. Empty or omitted = multi-server project request (Message required)." },
          "Email": { "type": "string", "format": "email", "description": "Requester's address, becomes Reply-To of the quote email." },
          "Message": { "type": "string" },
          "Configuration": { "type": "string", "description": "Optional short summary of the requirements, included in the email." }
        },
        "required": ["Email"]
      }
    }
  }
}
