{"openapi":"3.1.0","info":{"title":"Verity comparison API","summary":"Calibrated, bounded likelihood ratios for forensic surface comparison.","description":"**Verity** — calibrated, bounded **likelihood ratios** for forensic surface\ncomparison, across three calibrated domains: **striated** marks (bullet lands),\n**impressed** marks (cartridge breech faces), and **toolmark** marks (striated\ntoolmarks, e.g. screwdriver marks).\n\nEach domain calibrates against its own named reference population, so the mark type\nyou declare (`domain`) selects the reference the likelihood ratio is calibrated on.\nDeclaring the wrong domain — e.g. a striated toolmark sent as `striated` (bullet\nlands) — calibrates the score against the wrong population and yields an invalid LR;\npick the domain that matches the mark.\n\nUpload X3P 3-D topography scans; the engine decodes them with the native\n`verity_x3p` codec, scores the comparison, calibrates the score to a likelihood\nratio against a named reference population (with a characterized cost, *Cllr*, and\na bootstrap credible interval), and returns region-level **attribution** — the\nparts of the marks that drove the match.\n\nThe decision stays behind a glass-box statistical firewall: the reported LR is a\nmonotone, bounded transform of the score, interpretable regardless of how the score\nwas computed. It is *not* a claim about the error rate of forensic examination,\nwhich remains unknown.\n\nThe versioned **`/v1`** routes expose the algorithm's intermediate steps on request\n(`include=calibration,features,perland,trace,recipe`), a reproducible **recipe** —\nthe methods section as JSON, with a content **handle** — an applicability-domain guard\n(`/v1/scope`), and metadata on the deployed scorer config (`/v1/scorer-config`) and the\ncalibration references (`/v1/references`).\n\nEvery pipeline stage is also **independently addressable** as a content-hashed artifact\ngraph: `POST /v1/artifacts` a scan for a surface handle, then chain\n`/v1/steps/{preprocess,signature,areal-signature,align,features}` by handle — each\nintermediate fetchable at `/v1/artifacts/{handle}` — and `/v1/steps/calibrate` maps a\nscore to a bounded LR behind the calibration firewall (it refuses to calibrate a score\nwhose scorer-config hash doesn't match the reference's).\n\nFor AI agents, the same calibrated tools are served over **MCP** (Model Context Protocol)\nat **`/mcp`** — streamable HTTP, stateless — exposing `compare_marks`, `detect_mark_type`,\n`calibrate_score`, `list_references`, `scorer_config`, and `service_health`. Scans are\npassed inline as base64-encoded X3P (a hosted server can't read the agent's local files).\n\nWeb app: <https://verity.codes> · Method & references: <https://docs.verity.codes/method>\n","contact":{"name":"Verity","url":"https://verity.codes/"},"license":{"name":"MIT / Apache-2.0"},"version":"0.1.0"},"paths":{"/health":{"get":{"tags":["meta"],"summary":"Service health + calibrated domains","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/detect":{"post":{"tags":["meta"],"summary":"Suggest a mark type from one scan","description":"Suggest a mark type for one uploaded scan, from striation anisotropy — the\nstructure-tensor coherence of its 2-D FFT power (directional striae → `striated`;\nisotropic areal → `impressed`). The UI pre-selects it; the user confirms, since\nthe mark type picks the calibration reference.","operationId":"detect_detect_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_detect_detect_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetectResponse"}}}},"413":{"description":"Payload too large (per-file, total-request, or file-count limit).","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"415":{"description":"Unsupported media type — the upload is not a readable X3P.","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"429":{"description":"Rate limit exceeded; a `Retry-After` header gives the cool-down.","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/compare":{"post":{"tags":["compare"],"summary":"Compare two marks → calibrated likelihood-ratio report","description":"Compare two marks into a calibrated, bounded **likelihood ratio** with region-level attribution. (Lean payload; use `/v1/compare?include=...` for the inspectable intermediate computations.)\n\nNote: requests larger than the reverse-proxy request-body cap (~100 MB, the Cloudflare proxy limit) may be rejected upstream with a non-JSON (HTML) `413` before reaching the app — so an oversized upload can fail without the JSON `{\"detail\": …}` body the app returns for limits it enforces itself.","operationId":"compare_compare_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_compare_compare_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Compare Compare Post"},"example":{"domain":"striated","score":0.153,"score_kind":"bullet-contrast","likelihood_ratio":146.0,"log10_lr":2.16,"log10_lr_ci_lo":1.74,"log10_lr_ci_hi":2.16,"lr_ci_method":"bootstrap-clustered","n_sources":201,"direction":"same source","verbal":"moderately strong support for same source","lr_bound_log10":2.16,"lr_bound_hit":true,"reference":{"name":"pooled bullet-land reference (Hamby-252 & 173, Beretta, Phoenix)","n_km":146,"n_knm":1755,"cllr":0.193,"cllr_min":0.168,"auc":0.984,"diagnostics_protocol":"in-sample fit of the deployed reference"},"attribution":[{"x":0,"y":0,"w":40,"h":1,"corr":0.91,"x_frac":0.0,"y_frac":0.0,"w_frac":0.167,"h_frac":1.0}],"provenance":{"scorer":"bullet-contrast","domain":"striated","input_hashes":{"mark_a":["<sha256>"],"mark_b":["<sha256>"]}},"scope_note":"This is a calibrated weight of evidence on the pooled bullet-land reference population. It is not a verdict: it is one input to an examiner's judgment, alongside case context. It is not a claim about the error rate of striated examination, which remains unknown.","previews":{"a":"[[…]] downsampled height grids","b":"[[…]]"}}}}},"413":{"description":"Payload too large (per-file, total-request, or file-count limit).","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"415":{"description":"Unsupported media type — the upload is not a readable X3P.","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"429":{"description":"Rate limit exceeded; a `Retry-After` header gives the cool-down.","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/compare":{"post":{"tags":["v1"],"summary":"Compare two marks, optionally returning intermediate computations","description":"Same calibrated comparison as `/compare`, plus — on request — the algorithm's\nintermediate computations. `include` is a comma-separated subset of:\n\n- **calibration** — the reference Tippett curve + KM/KNM score histogram with\n  this comparison's score located on it (what makes the LR defensible).\n- **features** — the land×land CCF matrix and the structure features the score\n  is built from (`diag_contrast`, `offset_margin`, `lag_coherence`, …).\n- **perland** — per-land evidence on the winning diagonal (diagnostic; lands\n  are not independent, so the reportable LR is the aggregate).\n- **trace** — the full signature pipeline (raw → bandpassed → oriented →\n  signature) for the best-matching land of each bullet.\n- **recipe** — the reproducible *methods section as JSON*: every pipeline step,\n  its parameters, the engine version, the input/reference hashes, and a content\n  **handle** over the whole recipe (reproducibility as a hash-equality check). On\n  by default.\n\n`include=all` returns everything. The default is `calibration,recipe`. The response\nalways carries an applicability-domain `scope` annotation for the inputs.\n\n`scorer_config` is an optional JSON object of scorer-hyperparameter overrides\n(e.g. `{\"lambda_c\": 8e-6}`). The comparison is scored under it, but — the firewall —\nif its hash doesn't match the reference's, the calibrated LR is **refused**\n(`calibrated: false`, raw score returned); calibration is valid only on a reference\nbuilt under the same config.","operationId":"compare_v1_v1_compare_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_compare_v1_v1_compare_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Compare V1 V1 Compare Post"},"example":{"domain":"striated","score":0.153,"score_kind":"bullet-contrast","likelihood_ratio":146.0,"log10_lr":2.16,"log10_lr_ci_lo":1.74,"log10_lr_ci_hi":2.16,"lr_ci_method":"bootstrap-clustered","n_sources":201,"direction":"same source","verbal":"moderately strong support for same source","lr_bound_log10":2.16,"lr_bound_hit":true,"reference":{"name":"pooled bullet-land reference (Hamby-252 & 173, Beretta, Phoenix)","n_km":146,"n_knm":1755,"cllr":0.193,"cllr_min":0.168,"auc":0.984,"diagnostics_protocol":"in-sample fit of the deployed reference"},"attribution":[{"x":0,"y":0,"w":40,"h":1,"corr":0.91,"x_frac":0.0,"y_frac":0.0,"w_frac":0.167,"h_frac":1.0}],"provenance":{"scorer":"bullet-contrast","domain":"striated","input_hashes":{"mark_a":["<sha256>"],"mark_b":["<sha256>"]}},"scope_note":"This is a calibrated weight of evidence on the pooled bullet-land reference population. It is not a verdict: it is one input to an examiner's judgment, alongside case context. It is not a claim about the error rate of striated examination, which remains unknown.","previews":{"a":"[[…]] downsampled height grids","b":"[[…]]"}}}}},"413":{"description":"Payload too large (per-file, total-request, or file-count limit).","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"415":{"description":"Unsupported media type — the upload is not a readable X3P.","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"429":{"description":"Rate limit exceeded; a `Retry-After` header gives the cool-down.","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/scope":{"post":{"tags":["v1"],"summary":"Applicability-domain check for one scan","description":"Check whether one scan is inside the validated domain for `domain`\n(resolution, mark type, coverage, signal). Defaults to `mode=refuse`, which reports\nan out-of-domain scan as `admissible=false` — the basis for refusing a junk LR;\n`mode=warn` annotates only (always `admissible=true`).","operationId":"scope_v1_v1_scope_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_scope_v1_v1_scope_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Scope V1 V1 Scope Post"}}}},"413":{"description":"Payload too large (per-file, total-request, or file-count limit).","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"415":{"description":"Unsupported media type — the upload is not a readable X3P.","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"429":{"description":"Rate limit exceeded; a `Retry-After` header gives the cool-down.","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/compare/report.pdf":{"post":{"tags":["v1"],"summary":"Court-ready per-comparison PDF report","description":"Run the comparison and return a court-ready PDF: the calibrated LR with its\ncredible interval and verbal weight, the named-scope statement, the reference\nand its cost, the method/pipeline version, the SHA-256 provenance of the input\nscans, and the attribution overlay.","operationId":"compare_report_pdf_v1_compare_report_pdf_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_compare_report_pdf_v1_compare_report_pdf_post"}}},"required":true},"responses":{"200":{"description":"Successful Response"},"413":{"description":"Payload too large (per-file, total-request, or file-count limit).","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"415":{"description":"Unsupported media type — the upload is not a readable X3P.","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"429":{"description":"Rate limit exceeded; a `Retry-After` header gives the cool-down.","content":{"application/json":{"schema":{"properties":{"detail":{"type":"string"}},"type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/scorer-config":{"get":{"tags":["v1"],"summary":"The deployed scorer configuration + hash","description":"The exact hyperparameters the engine scores with (ISO roughness-band cutoffs, the\nCMR congruence thresholds, the scorer identity) and their content hash. A calibrated\nLR is only valid when a reference's `scorer_config_hash` matches this — so this is the\nvalue to check before trusting a cross-config comparison.","operationId":"scorer_config_v1_v1_scorer_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Scorer Config V1 V1 Scorer Config Get"}}}}}}},"/v1/references":{"get":{"tags":["v1"],"summary":"List calibrated reference populations + provenance","description":"Every bundled reference the API calibrates against, with the scorer-config hash it\nwas built under, its source datasets, and its diagnostics (Cllr/Cllr_min/AUC) — what,\nexactly, each likelihood ratio is calibrated on.","operationId":"references_v1_v1_references_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response References V1 V1 References Get"}}}}}}},"/v1/references/{reference_id}":{"get":{"tags":["v1"],"summary":"Provenance for one reference","description":"Provenance for one reference by id (`striated` = bullet lands, `impressed` = cartridge\nbreech faces, `toolmark` = striated toolmarks, `striated_single` = single bullet land).","operationId":"reference_v1_v1_references__reference_id__get","parameters":[{"name":"reference_id","in":"path","required":true,"schema":{"type":"string","title":"Reference Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reference V1 V1 References  Reference Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/artifacts":{"post":{"tags":["v1"],"summary":"Upload a scan → a content-addressed surface handle","description":"Decode an X3P upload into a surface and store it, returning its content handle.\nThe handle is the entry point to the step graph: pass it to `/v1/steps/*`.","operationId":"put_artifact_v1_artifacts_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_put_artifact_v1_artifacts_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Put Artifact V1 Artifacts Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/artifacts/{handle}":{"get":{"tags":["v1"],"summary":"Artifact record (kind + provenance)","operationId":"get_artifact_v1_artifacts__handle__get","parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string","title":"Handle"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Artifact V1 Artifacts  Handle  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/artifacts/{handle}/data":{"get":{"tags":["v1"],"summary":"Raw artifact bytes (.npy)","description":"The artifact's array as a NumPy ``.npy`` (load with ``numpy.load``). ETag is the\ncontent handle, so it is cacheable and self-verifying.","operationId":"get_artifact_data_v1_artifacts__handle__data_get","parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string","title":"Handle"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/artifacts/{handle}/preview":{"get":{"tags":["v1"],"summary":"Downsampled artifact preview","description":"A bounded, JSON-friendly view of the artifact: a decimated 1-D signature or a\ndownsampled 2-D grid (the full array is at `/data`).","operationId":"get_artifact_preview_v1_artifacts__handle__preview_get","parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string","title":"Handle"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Artifact Preview V1 Artifacts  Handle  Preview Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/steps/preprocess":{"post":{"tags":["v1"],"summary":"Form removal + roughness-band isolation","description":"ISO 25178 form removal (degree-2) + ISO 16610 roughness-band isolation → a\nbandpassed surface handle. The cutoffs are exposed for inspection; the deployed\ndefaults are the calibrated values.","operationId":"step_preprocess_v1_steps_preprocess_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_step_preprocess_v1_steps_preprocess_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Step Preprocess V1 Steps Preprocess Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/steps/signature":{"post":{"tags":["v1"],"summary":"Striated 1-D signature (the comparison object)","description":"The full striated pipeline for one land — form removal, roughness isolation,\nFFT orientation, groove crop — to the 1-D across-striae signature. The `preview` is\nbyte-identical to `include=trace` (it goes through the same `land_trace`).","operationId":"step_signature_v1_steps_signature_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_step_signature_v1_steps_signature_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Step Signature V1 Steps Signature Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/steps/areal-signature":{"post":{"tags":["v1"],"summary":"Impressed 2-D areal signature","description":"The impressed pipeline for one breech face → the fixed-size, unit-norm areal\nroughness map (the comparison object for the 2-D / CMR path).","operationId":"step_areal_signature_v1_steps_areal_signature_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_step_areal_signature_v1_steps_areal_signature_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Step Areal Signature V1 Steps Areal Signature Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/steps/align":{"post":{"tags":["v1"],"summary":"1-D alignment of two signatures (lag + CCF)","description":"Peak normalized cross-correlation of two 1-D signatures over integer lags — the\npairwise comparison primitive. Returns the best lag and the CCF at it.","operationId":"step_align_v1_steps_align_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_step_align_v1_steps_align_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Step Align V1 Steps Align Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/steps/features":{"post":{"tags":["v1"],"summary":"Bullet land×land CCF matrix + structure features","description":"Compare two bullets' lands (comma-separated `signature.1d` handles per side) into\nthe land×land CCF matrix and the structure features the score is built from\n(`diag_contrast`, `offset_margin`, `lag_coherence`, …). Identical serialization to\n`include=features`.","operationId":"step_features_v1_steps_features_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_step_features_v1_steps_features_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Step Features V1 Steps Features Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/steps/calibrate":{"post":{"tags":["v1"],"summary":"Calibrate a score → bounded LR (the firewall)","description":"Map a comparison score to a bounded likelihood ratio against a named reference —\nthe calibration firewall, as an addressable step. The reference (its KM/KNM scores)\nboth fits the monotone, ELUB-bounded calibration and scopes it.\n\nThe firewall: a calibrated LR is valid only if the score was produced under the\n*same* scorer config as the reference. If the caller declares a `scorer_config_hash`\nthat does **not** match the reference's, the LR is **refused** (`calibrated: false`,\nraw score passed through) — a mis-scaled number would be worse than none. If the hash\nmatches, the LR is emitted with `config_verified: true`; if omitted, the LR is emitted\nwith `config_verified: false` (the deployed pipeline uses the matching config, so the\ncommon case is sound, but the caller hasn't proven it). The bullet score to pass here\nis `features.diag_contrast`; the impressed score is the CMR-2D count.","operationId":"step_calibrate_v1_steps_calibrate_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_step_calibrate_v1_steps_calibrate_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Step Calibrate V1 Steps Calibrate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Body_compare_compare_post":{"properties":{"domain":{"type":"string","enum":["striated","impressed","toolmark"],"title":"Domain","description":"Calibration domain — selects the reference population the likelihood ratio is calibrated on; must match the mark type. `striated` = bullet lands; `impressed` = cartridge breech faces; `toolmark` = striated toolmarks (e.g. screwdriver marks)."},"mark_a":{"items":{"type":"string","contentMediaType":"application/octet-stream"},"type":"array","title":"Mark A"},"mark_b":{"items":{"type":"string","contentMediaType":"application/octet-stream"},"type":"array","title":"Mark B"}},"type":"object","required":["domain","mark_a","mark_b"],"title":"Body_compare_compare_post"},"Body_compare_report_pdf_v1_compare_report_pdf_post":{"properties":{"domain":{"type":"string","enum":["striated","impressed","toolmark"],"title":"Domain","description":"Calibration domain — selects the reference population the likelihood ratio is calibrated on; must match the mark type. `striated` = bullet lands; `impressed` = cartridge breech faces; `toolmark` = striated toolmarks (e.g. screwdriver marks)."},"mark_a":{"items":{"type":"string","contentMediaType":"application/octet-stream"},"type":"array","title":"Mark A"},"mark_b":{"items":{"type":"string","contentMediaType":"application/octet-stream"},"type":"array","title":"Mark B"},"case_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Case Id"},"examiner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Examiner"}},"type":"object","required":["domain","mark_a","mark_b"],"title":"Body_compare_report_pdf_v1_compare_report_pdf_post"},"Body_compare_v1_v1_compare_post":{"properties":{"domain":{"type":"string","enum":["striated","impressed","toolmark"],"title":"Domain","description":"Calibration domain — selects the reference population the likelihood ratio is calibrated on; must match the mark type. `striated` = bullet lands; `impressed` = cartridge breech faces; `toolmark` = striated toolmarks (e.g. screwdriver marks)."},"mark_a":{"items":{"type":"string","contentMediaType":"application/octet-stream"},"type":"array","title":"Mark A"},"mark_b":{"items":{"type":"string","contentMediaType":"application/octet-stream"},"type":"array","title":"Mark B"},"include":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Include"},"scorer_config":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scorer Config"}},"type":"object","required":["domain","mark_a","mark_b"],"title":"Body_compare_v1_v1_compare_post"},"Body_detect_detect_post":{"properties":{"scan":{"type":"string","contentMediaType":"application/octet-stream","title":"Scan"}},"type":"object","required":["scan"],"title":"Body_detect_detect_post"},"Body_put_artifact_v1_artifacts_post":{"properties":{"scan":{"type":"string","contentMediaType":"application/octet-stream","title":"Scan"}},"type":"object","required":["scan"],"title":"Body_put_artifact_v1_artifacts_post"},"Body_scope_v1_v1_scope_post":{"properties":{"scan":{"type":"string","contentMediaType":"application/octet-stream","title":"Scan"},"domain":{"type":"string","enum":["striated","impressed","toolmark"],"title":"Domain","description":"Calibration domain — selects the reference population the likelihood ratio is calibrated on; must match the mark type. `striated` = bullet lands; `impressed` = cartridge breech faces; `toolmark` = striated toolmarks (e.g. screwdriver marks)."},"mode":{"type":"string","title":"Mode","default":"refuse"}},"type":"object","required":["scan","domain"],"title":"Body_scope_v1_v1_scope_post"},"Body_step_align_v1_steps_align_post":{"properties":{"a":{"type":"string","title":"A"},"b":{"type":"string","title":"B"}},"type":"object","required":["a","b"],"title":"Body_step_align_v1_steps_align_post"},"Body_step_areal_signature_v1_steps_areal_signature_post":{"properties":{"surface":{"type":"string","title":"Surface"}},"type":"object","required":["surface"],"title":"Body_step_areal_signature_v1_steps_areal_signature_post"},"Body_step_calibrate_v1_steps_calibrate_post":{"properties":{"score":{"type":"number","title":"Score"},"reference":{"type":"string","title":"Reference"},"scorer_config_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scorer Config Hash"},"ci":{"type":"boolean","title":"Ci","default":true}},"type":"object","required":["score","reference"],"title":"Body_step_calibrate_v1_steps_calibrate_post"},"Body_step_features_v1_steps_features_post":{"properties":{"a":{"type":"string","title":"A"},"b":{"type":"string","title":"B"}},"type":"object","required":["a","b"],"title":"Body_step_features_v1_steps_features_post"},"Body_step_preprocess_v1_steps_preprocess_post":{"properties":{"surface":{"type":"string","title":"Surface"},"lambda_s":{"type":"number","title":"Lambda S","default":4e-06},"lambda_c":{"type":"number","title":"Lambda C","default":0.00025}},"type":"object","required":["surface"],"title":"Body_step_preprocess_v1_steps_preprocess_post"},"Body_step_signature_v1_steps_signature_post":{"properties":{"surface":{"type":"string","title":"Surface"}},"type":"object","required":["surface"],"title":"Body_step_signature_v1_steps_signature_post"},"DetectResponse":{"properties":{"domain":{"type":"string","title":"Domain"},"coherence":{"type":"number","title":"Coherence"}},"type":"object","required":["domain","coherence"],"title":"DetectResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status"},"engine_version":{"type":"string","title":"Engine Version"},"domains":{"items":{"type":"string"},"type":"array","title":"Domains"},"rate_limiter":{"$ref":"#/components/schemas/RateLimiterStats"}},"type":"object","required":["status","engine_version","domains","rate_limiter"],"title":"HealthResponse"},"RateLimiterStats":{"properties":{"tracked_ips":{"type":"integer","title":"Tracked Ips"}},"type":"object","required":["tracked_ips"],"title":"RateLimiterStats"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}},"tags":[{"name":"compare","description":"Compare two marks → a calibrated likelihood-ratio report."},{"name":"meta","description":"Service health and mark-type detection."},{"name":"v1","description":"Versioned API: comparison with inspectable intermediates."}]}