{
  "openapi": "3.1.0",
  "info": {
    "title": "V-NAND Similarity Search API",
    "version": "1.2.0",
    "description": "Log-scale VTH distribution shape retrieval. Only coherent distribution waveforms are analyzed; explanatory text, tables, empty coordinate systems, rectangles, and diagram shapes are rejected. The request image is processed only for this search and is not stored or used for training. Scores are calibrated similarity values, not probabilities."
  },
  "servers": [
    {
      "url": "https://dove9999.com"
    }
  ],
  "paths": {
    "/api/v1/similarity-search": {
      "get": {
        "summary": "Get API capability and input handling metadata",
        "responses": {
          "200": {
            "description": "Service metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Find V-NAND distribution images with similar shape",
        "description": "Accepts PNG or JPEG as raw bytes, multipart/form-data, or a Base64 data URL JSON body. Multiple charts at different coordinates are detected and cropped in reading order. Up to two independently validated color-derived full-width waveform series inside one physical chart are temporarily separated by color, normalized without color/style features, and ranked independently in `panel.series`. When three or more color-derived waveform series are detected, only the distribution with the highest irregularity score is exposed, ranked, and eligible for training as the single `panel.series` entry. Every returned series includes `trainingSelection`, a normalized 256-point `profile`, and its canonical `descriptor`; these fields plus the same complete image are sufficient to create one selected training request without a second Curve extractor. The panel and top-level compatibility fields mirror their representative series waveform and selector. Text, tables, empty axes, rectangles, and explanatory diagrams without a coherent distribution waveform are excluded; a request with no distribution waveform returns 422 `distribution_waveform_not_found`. The legacy panel `query` and `results` mirror its representative most-irregular series, and the top-level fields mirror panel 0's representative series. `topK` defaults to 8 and is limited to 1–10 per series. When the deployment configures `VTH_SEARCH_API_KEY`, send it as `Authorization: Bearer <key>` or `X-API-Key`.",
        "parameters": [
          {
            "name": "topK",
            "in": "query",
            "description": "Number of ranked candidates to return. Overrides a multipart or JSON body value.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 8
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "image/png": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/jpeg": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["image"],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "PNG or JPEG graph image"
                  },
                  "topK": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["imageDataUrl"],
                "properties": {
                  "imageDataUrl": {
                    "type": "string",
                    "format": "uri",
                    "description": "data:image/png;base64,... or data:image/jpeg;base64,..."
                  },
                  "topK": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "Search a sanitized document and select one training waveform",
            "source": "curl -sS -X POST 'https://dove9999.com/api/v1/similarity-search?topK=5' -H 'Content-Type: image/jpeg' --data-binary '@document.jpg' > search.json\njq '.panels[0].series[0] | {sourceSelection:.trainingSelection,profile,descriptor}' search.json"
          }
        ],
        "responses": {
          "200": {
            "description": "Ranked visual-shape matches",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/RequestError" },
          "401": { "$ref": "#/components/responses/RequestError" },
          "413": { "$ref": "#/components/responses/RequestError" },
          "415": { "$ref": "#/components/responses/RequestError" },
          "422": { "$ref": "#/components/responses/RequestError" },
          "500": { "$ref": "#/components/responses/RequestError" }
        }
      },
      "options": {
        "summary": "CORS preflight response",
        "responses": {
          "204": {
            "description": "CORS headers"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer"
      },
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    },
    "responses": {
      "RequestError": {
        "description": "Request or processing error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "HealthResponse": {
        "type": "object",
        "required": ["service", "version", "status", "method", "openapi", "multiChart", "inputHandling"],
        "properties": {
          "service": { "type": "string" },
          "version": { "type": "integer" },
          "status": { "type": "string", "example": "ok" },
          "method": { "type": "string", "example": "POST" },
          "openapi": { "type": "string" },
          "supportedImageTypes": { "type": "array", "items": { "type": "string" } },
          "maxImageBytes": { "type": "integer" },
          "maxRequestBodyBytes": { "type": "integer" },
          "maxResults": { "type": "integer" },
          "multiChart": {
            "type": "object",
            "required": ["supported", "ranking", "placement", "readingOrder", "lowResolutionRecovery", "nonChartRejection", "maxPanels", "overflowPolicy", "colorSeries"],
            "properties": {
              "supported": { "type": "boolean", "const": true },
              "ranking": { "type": "string", "const": "per-panel-per-series" },
              "placement": { "type": "string", "const": "arbitrary-non-overlapping" },
              "readingOrder": { "type": "string", "const": "top-to-bottom-left-to-right" },
              "lowResolutionRecovery": { "type": "boolean", "const": true },
              "nonChartRejection": { "type": "boolean", "const": true },
              "maxPanels": {
                "type": "integer",
                "const": 30,
                "description": "Maximum number of chart panels analyzed from one image."
              },
              "overflowPolicy": {
                "type": "string",
                "const": "highest-confidence-then-reading-order",
                "description": "When more than 30 credible panels are detected, retain the highest-confidence panels and return them in reading order."
              },
              "colorSeries": {
                "type": "object",
                "required": ["supported", "ranking", "styleInvariant", "representative", "maxIndependentSeries", "overflowPolicy"],
                "properties": {
                  "supported": { "type": "boolean", "const": true },
                  "ranking": { "type": "string", "const": "per-series" },
                  "styleInvariant": { "type": "boolean", "const": true },
                  "representative": { "type": "string", "const": "most-irregular" },
                  "maxIndependentSeries": {
                    "type": "integer",
                    "const": 2,
                    "description": "Up to two independently validated color-derived distributions are returned separately."
                  },
                  "overflowPolicy": {
                    "type": "string",
                    "const": "most-irregular-only",
                    "description": "When three or more color-derived distributions are detected, only the highest-irregularity distribution is returned, ranked, and exposed as a training target."
                  }
                }
              }
            }
          },
          "inputHandling": { "$ref": "#/components/schemas/InputHandling" }
        }
      },
      "InputHandling": {
        "type": "object",
        "required": ["stored", "usedForTraining", "processing"],
        "properties": {
          "stored": { "type": "boolean", "example": false },
          "usedForTraining": { "type": "boolean", "example": false },
          "processing": { "type": "string", "example": "transient-memory-only" }
        }
      },
      "QueryAnalysis": {
        "type": "object",
        "required": [
          "stateCount",
          "observedStateCount",
          "peakCount",
          "valleyCount",
          "topologyConsistent"
        ],
        "properties": {
          "stateCount": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20,
            "description": "Physical State count represented by the materialized waveform peaks."
          },
          "observedStateCount": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20,
            "description": "Raw peak evidence before bounded artifact regularization."
          },
          "peakCount": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20
          },
          "valleyCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 19
          },
          "topologyConsistent": {
            "type": "boolean",
            "description": "True when State, peak, valley, distance, width and outer-tail array lengths satisfy the topology contract."
          }
        },
        "additionalProperties": true
      },
      "SearchResponse": {
        "type": "object",
        "required": ["schemaVersion", "requestId", "service", "inputHandling", "warnings", "model", "query", "candidateCount", "matchedCandidateCount", "processingMs", "results", "trainingSelection", "profile", "descriptor", "panelCount", "panelLayout", "panelDetection", "panels"],
        "properties": {
          "schemaVersion": { "type": "integer", "example": 1 },
          "requestId": { "type": "string" },
          "service": { "type": "string", "const": "vth-similarity-search-api" },
          "inputHandling": { "$ref": "#/components/schemas/InputHandling" },
          "warnings": { "type": "array", "items": { "$ref": "#/components/schemas/Warning" } },
          "model": { "type": "object", "additionalProperties": true },
          "query": { "$ref": "#/components/schemas/QueryAnalysis" },
          "candidateCount": { "type": "integer" },
          "matchedCandidateCount": { "type": "integer" },
          "processingMs": { "type": "number" },
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/RankedResult" }
          },
          "trainingSelection": {
            "$ref": "#/components/schemas/TrainingSelection"
          },
          "profile": {
            "$ref": "#/components/schemas/TrainingProfile"
          },
          "descriptor": {
            "$ref": "#/components/schemas/TrainingWaveformDescriptor"
          },
          "panelCount": {
            "type": "integer",
            "minimum": 1,
            "maximum": 30,
            "description": "Number of independently detected and searched chart panels."
          },
          "panelLayout": { "$ref": "#/components/schemas/PanelLayout" },
          "panelDetection": {
            "type": "object",
            "required": ["fallbackUsed", "readingOrder", "detectedPanelCount", "rejectedNonChartCount", "analyzedPanelCount", "maxPanels", "truncated"],
            "properties": {
              "fallbackUsed": { "type": "boolean" },
              "readingOrder": {
                "type": "string",
                "const": "top-to-bottom-left-to-right"
              },
              "detectedPanelCount": {
                "type": "integer",
                "minimum": 0,
                "description": "Number of credible panels detected before applying the analysis limit."
              },
              "rejectedNonChartCount": {
                "type": "integer",
                "minimum": 0,
                "description": "Number of axis-like candidates excluded because no coherent distribution Curve was present."
              },
              "analyzedPanelCount": {
                "type": "integer",
                "minimum": 1,
                "maximum": 30,
                "description": "Number of panels independently analyzed and returned in panels."
              },
              "maxPanels": {
                "type": "integer",
                "const": 30,
                "description": "Maximum number of chart panels analyzed from one image."
              },
              "truncated": {
                "type": "boolean",
                "description": "True when detectedPanelCount exceeded maxPanels and lower-confidence panels were omitted."
              }
            }
          },
          "panels": {
            "type": "array",
            "minItems": 1,
            "maxItems": 30,
            "items": { "$ref": "#/components/schemas/PanelSearchResult" }
          }
        }
      },
      "TrainingSelection": {
        "type": "object",
        "additionalProperties": false,
        "required": ["panelIndex", "panelCount", "seriesIndex", "seriesCount"],
        "properties": {
          "panelIndex": {
            "type": "integer",
            "minimum": 0,
            "maximum": 29
          },
          "panelCount": {
            "type": "integer",
            "minimum": 1,
            "maximum": 30
          },
          "seriesIndex": {
            "type": "integer",
            "minimum": 0
          },
          "seriesCount": {
            "type": "integer",
            "minimum": 1
          }
        },
        "description": "Zero-based physical panel and intra-panel series coordinates. panelIndex must be less than panelCount and seriesIndex must be less than seriesCount."
      },
      "TrainingProfile": {
        "type": "array",
        "minItems": 256,
        "maxItems": 256,
        "items": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "description": "Server-extracted normalized 256-point Curve. Copy this field unchanged into the selected training request."
      },
      "TrainingWaveformDescriptor": {
        "type": "object",
        "required": ["stateCount", "peakLocations", "peakWidths", "valleyHeights", "valleyLocations", "valleyDepths", "valleyPositionRatios", "peakValleyDistances", "tailSlopes", "area"],
        "properties": {
          "stateCount": { "type": "integer", "minimum": 1, "maximum": 20 },
          "observedStateCount": { "type": "integer", "minimum": 1, "maximum": 20 },
          "regularized": { "type": "boolean" },
          "peakLocations": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": { "type": "number", "minimum": 0, "maximum": 1 }
          },
          "peakWidths": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 }
          },
          "valleyHeights": {
            "type": "array",
            "maxItems": 19,
            "items": { "type": "number", "minimum": 0, "maximum": 1 }
          },
          "valleyLocations": {
            "type": "array",
            "maxItems": 19,
            "items": { "type": "number", "minimum": 0, "maximum": 1 }
          },
          "valleyDepths": {
            "type": "array",
            "maxItems": 19,
            "items": { "type": "number", "minimum": 0, "maximum": 1 }
          },
          "valleyPositionRatios": {
            "type": "array",
            "maxItems": 19,
            "items": { "type": "number", "exclusiveMinimum": 0, "exclusiveMaximum": 1 }
          },
          "peakValleyDistances": {
            "type": "array",
            "maxItems": 38,
            "items": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 }
          },
          "tailSlopes": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "items": { "type": "number", "minimum": 0, "maximum": 1 }
          },
          "area": { "type": "number", "minimum": 0, "maximum": 1.5 }
        },
        "description": "Canonical descriptor rebuilt by the search service from profile. Copy it unchanged into the selected training request."
      },
      "PanelLayout": {
        "type": "object",
        "required": ["rows", "columns"],
        "properties": {
          "rows": { "type": "integer", "minimum": 1 },
          "columns": { "type": "integer", "minimum": 1 }
        }
      },
      "PanelBounds": {
        "type": "object",
        "required": ["x", "y", "width", "height"],
        "properties": {
          "x": { "type": "number", "minimum": 0 },
          "y": { "type": "number", "minimum": 0 },
          "width": { "type": "number", "exclusiveMinimum": 0 },
          "height": { "type": "number", "exclusiveMinimum": 0 }
        }
      },
      "PanelSearchResult": {
        "type": "object",
        "required": ["panelIndex", "bounds", "confidence", "detectionReason", "axisMode", "seriesCount", "selectedSeriesIndex", "series", "trainingSelection", "profile", "descriptor", "query", "matchedCandidateCount", "results"],
        "properties": {
          "panelIndex": {
            "type": "integer",
            "minimum": 0,
            "description": "Zero-based top-to-bottom, left-to-right reading order."
          },
          "bounds": {
            "type": "object",
            "required": ["processed", "source", "normalized"],
            "properties": {
              "processed": { "$ref": "#/components/schemas/PanelBounds" },
              "source": { "$ref": "#/components/schemas/PanelBounds" },
              "normalized": { "$ref": "#/components/schemas/PanelBounds" }
            }
          },
          "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
          "detectionReason": { "type": "string" },
          "axisMode": { "type": "string" },
          "seriesCount": {
            "type": "integer",
            "minimum": 1,
            "description": "Number of returned target waveform series inside this physical chart panel. Color-derived distributions return up to two independent series; three or more color-derived detections collapse to the single most-irregular series."
          },
          "selectedSeriesIndex": {
            "type": "integer",
            "minimum": 0,
            "description": "Representative most-irregular returned series mirrored by the compatibility query/results fields. It is 0 when three or more color-derived detections collapse to one target."
          },
          "series": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "#/components/schemas/SeriesSearchResult" }
          },
          "trainingSelection": {
            "$ref": "#/components/schemas/TrainingSelection"
          },
          "profile": {
            "$ref": "#/components/schemas/TrainingProfile",
            "description": "Representative series profile mirrored from series[selectedSeriesIndex]."
          },
          "descriptor": {
            "$ref": "#/components/schemas/TrainingWaveformDescriptor",
            "description": "Representative descriptor mirrored from series[selectedSeriesIndex]."
          },
          "query": { "$ref": "#/components/schemas/QueryAnalysis" },
          "matchedCandidateCount": { "type": "integer" },
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/RankedResult" }
          }
        }
      },
      "SeriesSearchResult": {
        "type": "object",
        "required": ["seriesIndex", "sourceIndex", "selected", "separationMode", "irregularityScore", "trainingSelection", "profile", "descriptor", "query", "matchedCandidateCount", "results"],
        "properties": {
          "seriesIndex": {
            "type": "integer",
            "minimum": 0,
            "description": "Zero-based independent waveform series index inside the panel."
          },
          "sourceIndex": {
            "type": "integer",
            "minimum": 0,
            "description": "Internal color or geometry candidate index used during extraction."
          },
          "selected": {
            "type": "boolean",
            "description": "True for the most-irregular representative retained by compatibility fields."
          },
          "separationMode": {
            "type": "string",
            "enum": ["color", "achromatic", "geometry", "chromatic-union", "single"]
          },
          "irregularityScore": {
            "type": "number",
            "minimum": 0
          },
          "trainingSelection": {
            "$ref": "#/components/schemas/TrainingSelection"
          },
          "profile": {
            "$ref": "#/components/schemas/TrainingProfile"
          },
          "descriptor": {
            "$ref": "#/components/schemas/TrainingWaveformDescriptor"
          },
          "query": { "$ref": "#/components/schemas/QueryAnalysis" },
          "matchedCandidateCount": { "type": "integer" },
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/RankedResult" }
          }
        }
      },
      "Warning": {
        "type": "object",
        "required": ["code", "message"],
        "properties": {
          "code": { "type": "string" },
          "message": { "type": "string" }
        }
      },
      "RankedResult": {
        "type": "object",
        "required": ["rank", "id", "label", "score", "scorePercent", "stateCount", "family", "learned", "storage", "scores", "reasons"],
        "properties": {
          "rank": { "type": "integer", "minimum": 1 },
          "id": { "type": "string" },
          "label": { "type": "string" },
          "score": { "type": "number", "minimum": 0, "maximum": 1 },
          "scorePercent": { "type": "number", "minimum": 0, "maximum": 100 },
          "stateCount": { "type": "integer" },
          "family": { "type": "string" },
          "learned": { "type": "boolean" },
          "storage": { "type": "string" },
          "imageUrl": { "type": ["string", "null"], "format": "uri" },
          "sourceImageUrl": { "type": ["string", "null"], "format": "uri" },
          "scores": { "type": "object", "additionalProperties": { "type": ["number", "null"] } },
          "curveHypothesisIndex": { "type": "integer" },
          "reasons": { "type": "array", "items": { "type": "string" }, "maxItems": 3 }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "requestId": { "type": "string" },
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "string" },
              "message": { "type": "string" },
              "reasonCode": {
                "type": "string",
                "description": "Machine-readable detailed reason. The legacy error.code remains stable for existing clients."
              },
              "details": {
                "$ref": "#/components/schemas/DiagnosticDetails"
              }
            }
          }
        }
      },
      "DiagnosticDetails": {
        "type": "object",
        "required": ["category", "diagnosticCode", "reason", "action", "diagnostics"],
        "properties": {
          "category": {
            "type": "string",
            "enum": ["input", "waveform_detection"]
          },
          "legacyCode": {
            "type": "string",
            "description": "Compatibility error code retained at error.code."
          },
          "reason": {
            "type": "string",
            "enum": [
              "image_required",
              "unsupported",
              "decode_failed",
              "resource_limit",
              "no_foreground",
              "low_resolution_insufficient",
              "table_lattice_dominant",
              "no_coherent_waveform",
              "candidates_rejected",
              "candidates_ambiguous"
            ]
          },
          "diagnosticCode": {
            "type": "string",
            "pattern": "^VTH-(IN|DETECT)-[A-Z-]+$",
            "example": "VTH-DETECT-TABLE-LATTICE"
          },
          "message": { "type": "string" },
          "action": {
            "type": "string",
            "description": "Suggested corrective action suitable for displaying directly to the user."
          },
          "diagnostics": {
            "type": "object",
            "description": "Compact numeric/boolean detector evidence such as input dimensions, foreground ratio, candidate counts, source scale, and table-lattice flags.",
            "additionalProperties": {
              "type": ["number", "boolean", "string"]
            }
          }
        }
      }
    }
  }
}
