{
  "info": {
    "name": "fixRAgent triage API",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "description": "One photo in, one fixed JSON triage out. POST /api/triage returns a 20-field core (what it is, whether a fault is visible, one of four urgency words, which trade, a line for the tenant), a diagnosis_id and a callback_url for the outcome.\n\n`severity` and `tier` are assigned by the server from a decision table, never taken from the model. `?config=fast` reads the photo three times (what production served on one measured call, 2026-09-13T13:36:18.496Z: `agreement.n` 3, `config_id` fast-a1v11-gemini-3.5-flash-lite-2026-09-12; a deployment without `ENGINE_V2_FAST=1` reads it once, and `agreement.n` on your reply says which you got) and `?config=deep` five times, independently; each reply reports how many of those reads agreed (`agreement.k` of `agreement.n`), a read that failed counts against agreement, the reply is `decided` when the answer most reads gave has at least half the reads asked for (`agreement.vote_rule` carries the rule), and an undecided first round takes a second round of reads and says so in `warnings`. Both configurations come from `api/_engine-v2-candidates.json`, shipped beside the function; `config_source` names it on every reply and `config_id` names the configuration (`fast-a1v11-gemini-3.5-flash-lite-2026-09-12` / `engine-v2.deep.gemini-3-5-flash-lite.a1_rubric_v11.n5`, engine `engine-v2-vote-1.0.0`). When that file cannot be served a hardcoded fallback answers and says so in four places, including a `config_id` that begins with `fallback-`; neither example here carries it. Both examples were captured handler-direct on the build machine on 2026-09-13 through the handler code on main (35ba8bf5), not on production; read `config_source` on a live reply for what the deployment served you.\n\nGuards, in order: 20 requests per IP per 1 hour (429), one demo key in `x-triage-key` at 60 requests per UTC day (401 / 429), a $20-per-day COMPUTED spend fuse (503, plain sentence, Retry-After to 00:00 UTC). Photos: base64 in `image`, 1 KB to 3,145,728 bytes decoded, JPEG/PNG/WebP; metadata is stripped before anything is hashed, sent or stored, and a photo that cannot be stripped is refused.\n\nEvery example response here was captured from the real handler on 2026-09-13. Nothing here is a bill or a service level. Docs and field notes: https://fixragent.com/docs · API terms: https://fixragent.com/api-terms · demo keys are issued by hand today (request form on the docs page).",
    "version": "engine-v2-vote-1.0.0"
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://b1442b0b-4a7a-4135-ab4d-9c4b9c1ab0d2.mock.pstmn.io",
      "description": "The public mock: it replays the captured examples, needs no key and runs no engine. Set it to https://fixragent.com for the live door — every request then runs for real, and \"Report the outcome\" writes a row."
    },
    {
      "key": "diagnosis_id",
      "value": "",
      "description": "A diagnosis_id from your own triage reply — the outcome request posts it, and on production that write is real."
    },
    {
      "key": "TRIAGE_DEMO_KEY",
      "value": "",
      "description": "The demo key, issued by hand on request (https://fixragent.com/docs). It is one key shared by everyone trying the door, and its 60 requests per UTC day are shared too. Sent as the x-triage-key header."
    },
    {
      "key": "photo_base64",
      "value": "",
      "description": "The photo, base64 (a data:image/...;base64, prefix is accepted). Decoded size 1 KB to 3 MB."
    }
  ],
  "item": [
    {
      "name": "Triage a photo — three reads (config=fast)",
      "description": "The photo read three times, independently, and tallied. `agreement.mode` is \"fast\", `agreement.n` is 3, and `agreement.k` of them gave the answer the reply delivers. `extended=1` adds the rest of the contract (the four graded readings, observations with boxes, parts, differential). `core.resident_explanation` is the engine's own plain-language line, delivered raw: the shipping card does not print it (on a no-fault reply it can assert absence, as this example does) and prints its own fixed no-fault sentence instead.",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "x-triage-key",
            "value": "{{TRIAGE_DEMO_KEY}}"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/api/triage?config=fast&extended=1",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "api",
            "triage"
          ],
          "query": [
            {
              "key": "config",
              "value": "fast"
            },
            {
              "key": "extended",
              "value": "1"
            }
          ]
        },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"image\": \"{{photo_base64}}\",\n  \"mimeType\": \"image/jpeg\",\n  \"reporter\": \"resident\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      },
      "response": [
        {
          "name": "200 — three of three reads agreed, WHENEVER (fast-a1v11-gemini-3.5-flash-lite-2026-09-12)",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "x-triage-key",
                "value": "{{TRIAGE_DEMO_KEY}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/triage?config=fast&extended=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "triage"
              ],
              "query": [
                {
                  "key": "config",
                  "value": "fast"
                },
                {
                  "key": "extended",
                  "value": "1"
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"image\": \"{{photo_base64}}\",\n  \"mimeType\": \"image/jpeg\",\n  \"reporter\": \"resident\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "status": "OK",
          "code": 200,
          "_postman_previewlanguage": "json",
          "header": [
            {
              "key": "content-type",
              "value": "application/json; charset=utf-8"
            },
            {
              "key": "cache-control",
              "value": "no-store"
            }
          ],
          "body": "{\n  \"diagnosis_id\": \"cce64cbc-97b2-410d-a761-2a8d65d570ad\",\n  \"engine_version\": \"engine-v2-vote-1.0.0\",\n  \"rubric_version\": \"v1.1-2026-09-11\",\n  \"config_id\": \"fast-a1v11-gemini-3.5-flash-lite-2026-09-12\",\n  \"config_source\": {\n    \"candidates_file\": \"api/_engine-v2-candidates.json\",\n    \"candidates_file_present\": true,\n    \"requested\": \"fast\",\n    \"fallback_used\": false,\n    \"fallback_reason\": null,\n    \"warning\": null,\n    \"vote\": true\n  },\n  \"agreement\": {\n    \"mode\": \"fast\",\n    \"n\": 3,\n    \"k\": 3,\n    \"decided\": true,\n    \"votes\": {\n      \"fault\": 0,\n      \"no_fault\": 3,\n      \"no_verdict\": 0\n    },\n    \"vote_rule\": \"decided when the majority verdict has k >= ceil(n/2) parsed votes; n = reads attempted, failed reads count in n and never in k; a tie is never decided; undecided re-votes once\",\n    \"re_voted\": false,\n    \"reported_round\": 1,\n    \"rounds\": [\n      {\n        \"round\": 1,\n        \"n\": 3,\n        \"reads_parsed\": 3,\n        \"read_failures\": [],\n        \"k\": 3,\n        \"decided\": true,\n        \"votes\": {\n          \"fault\": 0,\n          \"no_fault\": 3,\n          \"no_verdict\": 0\n        },\n        \"reason\": \"k >= ceil(n/2)\",\n        \"reported\": true\n      }\n    ],\n    \"reads_attempted_total\": 3,\n    \"majority_verdict\": false,\n    \"need\": 2,\n    \"verdict_field\": \"grounded fault_detected (contract R1 visible-in-photo + R3 taxonomy class and summary), majority per a2_selfcons_v11 over parsed reads\",\n    \"delivered_matches_majority\": true,\n    \"applicable\": true\n  },\n  \"callback_url\": \"/api/outcome?diagnosis_id=cce64cbc-97b2-410d-a761-2a8d65d570ad\",\n  \"callback\": {\n    \"method\": \"POST\",\n    \"url\": \"/api/outcome\",\n    \"note\": \"api/outcome.js reads diagnosis_id from the JSON BODY on POST; the query string on callback_url is there so the link identifies the diagnosis to a human and in logs, not because the endpoint reads it.\",\n    \"required_body\": {\n      \"diagnosis_id\": \"cce64cbc-97b2-410d-a761-2a8d65d570ad\",\n      \"outcome\": [\n        \"fixed\",\n        \"not_fixed\",\n        \"partial\"\n      ],\n      \"reported_via\": [\n        \"results_screen\",\n        \"scan_log\",\n        \"triage_link\"\n      ]\n    }\n  },\n  \"core\": {\n    \"is_building_asset\": true,\n    \"asset_type\": \"HVAC Air Handler and Furnace\",\n    \"asset_category\": \"HVAC\",\n    \"photo_subject\": \"HVAC air handler and furnace unit with connected ductwork and piping\",\n    \"report_photo_agreement\": null,\n    \"fault_detected\": false,\n    \"fault_visible_in_photo\": false,\n    \"fault_summary\": \"\",\n    \"fault_classes\": [],\n    \"severity\": \"P4\",\n    \"tier\": \"WHENEVER\",\n    \"criterion_1_fired\": false,\n    \"tier_forced_by\": null,\n    \"visible_wiring\": false,\n    \"water_near_electrical\": false,\n    \"safety_hazard\": false,\n    \"hazard_detail\": null,\n    \"trade_required\": null,\n    \"confidence\": 1,\n    \"resident_explanation\": \"Your HVAC system is currently in a healthy state with no visible faults or active damage.\"\n  },\n  \"extended\": {\n    \"isHighRisk\": false,\n    \"hazard_class\": \"H0\",\n    \"active_loss\": \"L0\",\n    \"habitability\": \"C0\",\n    \"deferral_cost\": \"D0\",\n    \"severity_reason_row\": 7,\n    \"make_model\": null,\n    \"identification_reasoning\": \"Furnace cabinet, evaporator coil casing, and sheet metal ductwork are clearly visible.\",\n    \"observations\": [\n      {\n        \"label\": \"HVAC Air Handler\",\n        \"box\": [\n          300,\n          260,\n          700,\n          800\n        ],\n        \"confidence\": 0.95\n      },\n      {\n        \"label\": \"Furnace Unit\",\n        \"box\": [\n          700,\n          250,\n          1000,\n          810\n        ],\n        \"confidence\": 0.95\n      }\n    ],\n    \"loto_required\": false,\n    \"skill_level\": null,\n    \"diy_appropriate\": true,\n    \"estimated_duration_minutes\": 0,\n    \"parts_manifest\": [],\n    \"resident_why_it_matters\": \"Routine checks ensure your heating and cooling systems operate efficiently and last as long as possible.\",\n    \"resident_do_not\": null,\n    \"resident_what_happens_next\": \"No immediate action is required. Continue regular seasonal filter changes and maintenance.\",\n    \"root_cause\": null,\n    \"failure_mechanism\": null,\n    \"differential\": [],\n    \"wiring_water_reason\": \"none visible; none visible\"\n  },\n  \"contract_validation\": {\n    \"contract_version\": \"v1-DRAFT-2026-09-10\",\n    \"policy\": \"repair-and-record\",\n    \"policy_note\": \"A violation is REPAIRED and the repair is listed below. Repairs never invent a photo-grounded fact, a taxonomy class or prose; where the engine gave no support for an assertion the assertion is retracted and preserved under `retracted`. If any violation survives repair the whole response is refused (502 contract_unrepairable) rather than emitted.\",\n    \"rules_source\": {\n      \"file\": \"qa/engine-v2/CONTRACT-v1.json\",\n      \"rules_in_contract\": 8,\n      \"predicates_implemented\": 8,\n      \"drift\": [],\n      \"schema_fields_in_contract\": 51,\n      \"schema_keywords_enforced\": [\n        \"type\",\n        \"enum\",\n        \"pattern\",\n        \"minimum\",\n        \"maximum\",\n        \"minLength\",\n        \"minItems\",\n        \"maxItems\",\n        \"uniqueItems\",\n        \"items\",\n        \"properties\",\n        \"required\",\n        \"additionalProperties\"\n      ],\n      \"schema_keywords_ignored_as_annotation\": [\n        \"$schema\",\n        \"$id\",\n        \"title\",\n        \"description\",\n        \"format\"\n      ],\n      \"shape_drift\": []\n    },\n    \"scope_checked\": {\n      \"extended_requested\": true,\n      \"has_report\": false,\n      \"clauses_not_evaluated\": [],\n      \"core_fields_not_governed_by_contract\": [\n        \"tier\",\n        \"criterion_1_fired\",\n        \"tier_forced_by\",\n        \"visible_wiring\",\n        \"water_near_electrical\"\n      ],\n      \"contract_required_fields_this_door_does_not_emit\": [\n        \"category\",\n        \"diagnosis\",\n        \"difficulty\",\n        \"rootCause\",\n        \"materials\",\n        \"steps\",\n        \"proTip\",\n        \"estimatedDIYCost\",\n        \"estimatedProCost\",\n        \"youtubeVideoId\"\n      ],\n      \"contract_required_scope_note\": \"json_schema.required is the CONSUMER surface’s required set (the PWA render keys). This door emits core + extended, a different projection; the fields listed above are surfaces this door is not, and their absence is not a violation. Every contract-required field the door DOES emit is checked.\"\n    },\n    \"violations_found\": [],\n    \"shape_violations_found\": [],\n    \"contract_conflicts\": [],\n    \"repairs\": [],\n    \"exemptions\": [],\n    \"retracted\": {},\n    \"repairs_changed_severity\": false,\n    \"severity_before_repair\": \"P4\",\n    \"conformant\": true,\n    \"residual_violations\": [],\n    \"severity_after_repair\": \"P4\"\n  },\n  \"provenance\": {\n    \"contract_version\": \"v1-DRAFT-2026-09-10\",\n    \"models\": {\n      \"identification\": \"gemini-3.5-flash-lite\",\n      \"reasoning\": \"gemini-3.5-flash-lite\"\n    },\n    \"temperature\": 0.2,\n    \"prompt_hash\": \"b52b0e83c8d01ccb\",\n    \"prompt_base\": \"api/_engine-v2-fast.json system_prompt (sha256 93f5cb191f1c14d46ab33a003622fe94e32feb069e29c61fb69c3c0be70d3f56) + user_text — the run-2 a1_rubric_v11 export, held to the harness by qa/engine-v2-fast/check-export-drift.py; the resident report, when sent, rides in the user text (api/_engine-v2-fast.js USER_TEXT_WITH_REPORT, unmeasured variant)\",\n    \"agreement\": {\n      \"mode\": \"fast\",\n      \"n\": 3,\n      \"k\": 3,\n      \"decided\": true,\n      \"votes\": {\n        \"fault\": 0,\n        \"no_fault\": 3,\n        \"no_verdict\": 0\n      },\n      \"vote_rule\": \"decided when the majority verdict has k >= ceil(n/2) parsed votes; n = reads attempted, failed reads count in n and never in k; a tie is never decided; undecided re-votes once\",\n      \"re_voted\": false,\n      \"reported_round\": 1,\n      \"rounds\": [\n        {\n          \"round\": 1,\n          \"n\": 3,\n          \"reads_parsed\": 3,\n          \"read_failures\": [],\n          \"k\": 3,\n          \"decided\": true,\n          \"votes\": {\n            \"fault\": 0,\n            \"no_fault\": 3,\n            \"no_verdict\": 0\n          },\n          \"reason\": \"k >= ceil(n/2)\",\n          \"reported\": true\n        }\n      ],\n      \"reads_attempted_total\": 3,\n      \"majority_verdict\": false,\n      \"need\": 2,\n      \"verdict_field\": \"grounded fault_detected (contract R1 visible-in-photo + R3 taxonomy class and summary), majority per a2_selfcons_v11 over parsed reads\",\n      \"delivered_matches_majority\": true,\n      \"applicable\": true\n    },\n    \"prompt_addendum\": \"none — the RUBRIC v1.1 axes and criterion-1 facts are asked for by the exported system prompt and enforced by responseSchema (sha256 559265bb8d66ae4fbe98d756b0f574fb51a042997388c4b58ff630071a04991b); the model is never asked for severity or tier\",\n    \"latency_ms\": 4174,\n    \"photo_sha256\": \"a4232950f74c029d219eacff3d0f84ad86744c3c584b3c2fdafb49afbfeed913\",\n    \"exif_strip_version\": \"exif-strip-v3-2026-09-11\",\n    \"model_proposed_severity\": null,\n    \"severity_assigned_by\": \"server:RUBRIC-v1.1-2026-09-11 via a2_selfcons_v11 vote (assignSeverityV11 on the voted facts)\",\n    \"severity_overwritten\": false,\n    \"spend_today\": {\n      \"computed_usd\": 0.04412,\n      \"limit_usd\": 20,\n      \"limit_source\": \"default\",\n      \"basis\": \"computed_from_usage_tokens_x_declared_prices_not_metered; scope=this_warm_instance_only (in-process counter; cross-instance day spend is under-counted by up to the number of warm instances)\",\n      \"token_estimate\": true\n    },\n    \"vote\": {\n      \"candidate\": \"a1_rubric_v11\",\n      \"n\": 3,\n      \"rounds\": [\n        {\n          \"round\": 1,\n          \"n\": 3,\n          \"reads_parsed\": 3,\n          \"read_failures\": [],\n          \"k\": 3,\n          \"decided\": true,\n          \"votes\": {\n            \"fault\": 0,\n            \"no_fault\": 3,\n            \"no_verdict\": 0\n          },\n          \"reason\": \"k >= ceil(n/2)\",\n          \"reported\": true\n        }\n      ],\n      \"tally\": {\n        \"n_votes_parsed\": 3,\n        \"n_asked\": 3,\n        \"fault_detected\": {\n          \"true\": 0,\n          \"n\": 3,\n          \"voted\": false\n        },\n        \"is_building_asset\": {\n          \"true\": 3,\n          \"n\": 3,\n          \"voted\": true\n        },\n        \"n_agreeing\": 3,\n        \"base_pool\": \"raw-matching agreeing reads\",\n        \"rubric_axes_pool\": {\n          \"indices\": [\n            0,\n            1,\n            2\n          ],\n          \"rule\": \"all reads\"\n        },\n        \"fault_classes\": {\n          \"counts\": {},\n          \"voted\": []\n        },\n        \"safety_hazard\": {\n          \"true\": 0,\n          \"n\": 3,\n          \"voted\": false\n        },\n        \"fault_visible_in_photo\": {\n          \"true\": 0,\n          \"n\": 3,\n          \"voted\": false\n        },\n        \"rubric_axes\": {\n          \"hazard_class\": {\n            \"counts\": {\n              \"H0\": 3\n            },\n            \"voted\": \"H0\",\n            \"tie_rule\": \"outright\"\n          },\n          \"active_loss\": {\n            \"counts\": {\n              \"L0\": 3\n            },\n            \"voted\": \"L0\",\n            \"tie_rule\": \"outright\"\n          },\n          \"habitability\": {\n            \"counts\": {\n              \"C0\": 3\n            },\n            \"voted\": \"C0\",\n            \"tie_rule\": \"outright\"\n          },\n          \"deferral_cost\": {\n            \"counts\": {\n              \"D0\": 3\n            },\n            \"voted\": \"D0\",\n            \"tie_rule\": \"outright\"\n          }\n        },\n        \"criterion_1\": {\n          \"visible_wiring\": {\n            \"true\": 0,\n            \"n\": 3,\n            \"voted\": false,\n            \"tie_rule\": \"tie -> FALSE (v1.1 §2.1 uncertain does not fire)\"\n          },\n          \"water_near_electrical\": {\n            \"true\": 0,\n            \"n\": 3,\n            \"voted\": false,\n            \"tie_rule\": \"tie -> FALSE (v1.1 §2.1 uncertain does not fire)\"\n          }\n        },\n        \"base_vote_index\": 0,\n        \"fault_prose_cleared\": [],\n        \"confidence_agreement_fraction\": 1,\n        \"decision\": {\n          \"n\": 3,\n          \"k\": 3,\n          \"need\": 2,\n          \"decided\": true,\n          \"votes\": {\n            \"fault\": 0,\n            \"no_fault\": 3,\n            \"no_verdict\": 0\n          },\n          \"majority_verdict\": false,\n          \"reason\": \"k >= ceil(n/2)\"\n        }\n      },\n      \"rubric\": {\n        \"rubric_version\": \"v1.1-2026-09-11\",\n        \"rubric_v11_sha256\": \"f5968d73f21b1488da9fb25b1568ed7efdfa7cc888b4cec56b20c2b72a4ee951\",\n        \"rubric_axes\": {\n          \"hazard_class\": \"H0\",\n          \"active_loss\": \"L0\",\n          \"habitability\": \"C0\",\n          \"deferral_cost\": \"D0\",\n          \"hazard_reason\": \"none visible\",\n          \"active_loss_reason\": \"no active leaks or losses visible\",\n          \"habitability_reason\": \"unit is intact and operational\",\n          \"deferral_reason\": \"nothing changes in a week\"\n        },\n        \"rubric_axes_invalid\": {},\n        \"criterion_1\": {\n          \"visible_wiring\": false,\n          \"water_near_electrical\": false,\n          \"visible_wiring_reason\": \"none visible\",\n          \"water_near_electrical_reason\": \"none visible\"\n        },\n        \"criterion_1_invalid\": {},\n        \"criterion_1_fired\": false,\n        \"criterion_1_facts_fired\": [],\n        \"severity_reason_row\": 7,\n        \"step\": \"2:v1-row-7\",\n        \"rubric_p_uncapped\": \"P4\",\n        \"severity_capped_by\": null,\n        \"tier_forced_by\": null,\n        \"illegal_p1_no_fault\": false,\n        \"severity_source\": \"assignSeverityV11 on VOTED facts (never voted, never model-emitted)\",\n        \"per_vote_severity\": [\n          \"P4\",\n          \"P4\",\n          \"P4\"\n        ],\n        \"severity_final\": \"P4\",\n        \"tier_final\": \"WHENEVER\"\n      },\n      \"per_read\": [\n        {\n          \"fault_detected\": false,\n          \"fault_visible_in_photo\": false,\n          \"fault_classes\": [],\n          \"severity\": \"P4\",\n          \"tier\": \"WHENEVER\",\n          \"criterion_1_fired\": false,\n          \"is_building_asset\": true,\n          \"finish_reason\": \"STOP\",\n          \"attempts\": 1,\n          \"latency_ms\": 3989,\n          \"usage\": {\n            \"prompt_tokens\": 9572,\n            \"output_tokens\": 976\n          }\n        },\n        {\n          \"fault_detected\": false,\n          \"fault_visible_in_photo\": false,\n          \"fault_classes\": [],\n          \"severity\": \"P4\",\n          \"tier\": \"WHENEVER\",\n          \"criterion_1_fired\": false,\n          \"is_building_asset\": true,\n          \"finish_reason\": \"STOP\",\n          \"attempts\": 1,\n          \"latency_ms\": 4154,\n          \"usage\": {\n            \"prompt_tokens\": 9572,\n            \"output_tokens\": 1139\n          }\n        },\n        {\n          \"fault_detected\": false,\n          \"fault_visible_in_photo\": false,\n          \"fault_classes\": [],\n          \"severity\": \"P4\",\n          \"tier\": \"WHENEVER\",\n          \"criterion_1_fired\": false,\n          \"is_building_asset\": true,\n          \"finish_reason\": \"STOP\",\n          \"attempts\": 1,\n          \"latency_ms\": 4170,\n          \"usage\": {\n            \"prompt_tokens\": 9572,\n            \"output_tokens\": 1066\n          }\n        }\n      ],\n      \"read_failures\": [],\n      \"confidence_definition\": \"agreement fraction on the fault verdict over the reads that parsed (a2_selfcons_v11) — a vote count, not a model number\"\n    }\n  },\n  \"stored\": {\n    \"written\": false,\n    \"degraded\": false,\n    \"reason\": \"supabase_not_configured\",\n    \"table\": \"diy_guest_scans\"\n  },\n  \"warnings\": []\n}"
        },
        {
          "name": "401 demo_key_required — wrong or missing x-triage-key",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "x-triage-key",
                "value": "{{TRIAGE_DEMO_KEY}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/triage?config=fast&extended=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "triage"
              ],
              "query": [
                {
                  "key": "config",
                  "value": "fast"
                },
                {
                  "key": "extended",
                  "value": "1"
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"image\": \"{{photo_base64}}\",\n  \"mimeType\": \"image/jpeg\",\n  \"reporter\": \"resident\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "status": "Unauthorized",
          "code": 401,
          "_postman_previewlanguage": "json",
          "header": [
            {
              "key": "content-type",
              "value": "application/json; charset=utf-8"
            }
          ],
          "body": "{\n  \"error\": \"demo_key_required\",\n  \"message\": \"A valid key is required. Send it in the x-triage-key header. If you sent one, it was not accepted.\"\n}"
        },
        {
          "name": "400 image_required — no \"image\" field",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "x-triage-key",
                "value": "{{TRIAGE_DEMO_KEY}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/triage?config=fast&extended=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "triage"
              ],
              "query": [
                {
                  "key": "config",
                  "value": "fast"
                },
                {
                  "key": "extended",
                  "value": "1"
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"image\": \"{{photo_base64}}\",\n  \"mimeType\": \"image/jpeg\",\n  \"reporter\": \"resident\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "status": "Bad Request",
          "code": 400,
          "_postman_previewlanguage": "json",
          "header": [
            {
              "key": "content-type",
              "value": "application/json; charset=utf-8"
            }
          ],
          "body": "{\n  \"error\": \"image_required\",\n  \"message\": \"A photo is required. Send it as base64 in the \\\"image\\\" field.\"\n}"
        },
        {
          "name": "413 image_too_large — decoded photo over 3,145,728 bytes",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "x-triage-key",
                "value": "{{TRIAGE_DEMO_KEY}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/triage?config=fast&extended=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "triage"
              ],
              "query": [
                {
                  "key": "config",
                  "value": "fast"
                },
                {
                  "key": "extended",
                  "value": "1"
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"image\": \"{{photo_base64}}\",\n  \"mimeType\": \"image/jpeg\",\n  \"reporter\": \"resident\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "status": "Payload Too Large",
          "code": 413,
          "_postman_previewlanguage": "json",
          "header": [
            {
              "key": "content-type",
              "value": "application/json; charset=utf-8"
            }
          ],
          "body": "{\n  \"error\": \"image_too_large\",\n  \"message\": \"That photo is larger than 3MB. Please send a smaller one.\",\n  \"max_photo_bytes\": 3145728,\n  \"platform_request_body_limit\": {\n    \"bytes\": 4500000,\n    \"stated_as\": \"4.5 MB\",\n    \"source\": \"https://vercel.com/docs/functions/limitations\",\n    \"source_last_updated\": \"August 24, 2026\",\n    \"fetched\": \"2026-09-11\",\n    \"evidence\": \"cited from vendor documentation fetched on this date — NOT measured against a deployed function (nothing deploys from this branch)\"\n  }\n}"
        },
        {
          "name": "503 budget_exhausted — the daily spend fuse tripped (Retry-After to 00:00 UTC)",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "x-triage-key",
                "value": "{{TRIAGE_DEMO_KEY}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/triage?config=fast&extended=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "triage"
              ],
              "query": [
                {
                  "key": "config",
                  "value": "fast"
                },
                {
                  "key": "extended",
                  "value": "1"
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"image\": \"{{photo_base64}}\",\n  \"mimeType\": \"image/jpeg\",\n  \"reporter\": \"resident\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "status": "Service Unavailable",
          "code": 503,
          "_postman_previewlanguage": "json",
          "header": [
            {
              "key": "content-type",
              "value": "application/json; charset=utf-8"
            },
            {
              "key": "retry-after",
              "value": "57600"
            }
          ],
          "body": "{\n  \"error\": \"budget_exhausted\",\n  \"message\": \"The triage demo has used up its compute budget for today. This is a spending limit on our side, not a problem with your photo or your request. The budget resets at 00:00 UTC — please try again then.\",\n  \"resets_at\": \"2026-09-14T00:00:00.000Z\",\n  \"budget\": {\n    \"day\": \"2026-09-13\",\n    \"computed_usd\": 21,\n    \"limit_usd\": 20,\n    \"limit_source\": \"default\",\n    \"basis\": \"computed_from_usage_tokens_x_declared_prices_not_metered; scope=this_warm_instance_only (in-process counter; cross-instance day spend is under-counted by up to the number of warm instances)\"\n  },\n  \"retry_after_seconds\": 57600,\n  \"retry_after_basis\": \"utc_day\"\n}"
        }
      ]
    },
    {
      "name": "Triage a photo — five reads (config=deep)",
      "description": "The same photo read five times, independently, and tallied fact by fact. `agreement.k` of `agreement.n` reads gave the delivered yes/no answer about a fault; a read that failed counts against agreement; the reply is `decided` when that answer has at least half the reads asked for, a tie is never decided, and an undecided first round takes a second round of reads (`agreement.rounds`, `warnings`). `core.resident_explanation` is model prose delivered raw; the Triage Profile never prints it.",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "x-triage-key",
            "value": "{{TRIAGE_DEMO_KEY}}"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/api/triage?config=deep&extended=1",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "api",
            "triage"
          ],
          "query": [
            {
              "key": "config",
              "value": "deep"
            },
            {
              "key": "extended",
              "value": "1"
            }
          ]
        },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"image\": \"{{photo_base64}}\",\n  \"mimeType\": \"image/jpeg\",\n  \"reporter\": \"resident\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      },
      "response": [
        {
          "name": "200 — five of five reads agreed, WHENEVER (engine-v2.deep.gemini-3-5-flash-lite.a1_rubric_v11.n5)",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "x-triage-key",
                "value": "{{TRIAGE_DEMO_KEY}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/triage?config=deep&extended=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "triage"
              ],
              "query": [
                {
                  "key": "config",
                  "value": "deep"
                },
                {
                  "key": "extended",
                  "value": "1"
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"image\": \"{{photo_base64}}\",\n  \"mimeType\": \"image/jpeg\",\n  \"reporter\": \"resident\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "status": "OK",
          "code": 200,
          "_postman_previewlanguage": "json",
          "header": [
            {
              "key": "content-type",
              "value": "application/json; charset=utf-8"
            },
            {
              "key": "cache-control",
              "value": "no-store"
            }
          ],
          "body": "{\n  \"diagnosis_id\": \"2a0a447e-9ef1-4ec2-bde0-46ef5e7088bb\",\n  \"engine_version\": \"engine-v2-vote-1.0.0\",\n  \"rubric_version\": \"v1.1-2026-09-11\",\n  \"config_id\": \"engine-v2.deep.gemini-3-5-flash-lite.a1_rubric_v11.n5\",\n  \"config_source\": {\n    \"candidates_file\": \"api/_engine-v2-candidates.json\",\n    \"candidates_file_present\": true,\n    \"requested\": \"deep\",\n    \"fallback_used\": false,\n    \"fallback_reason\": null,\n    \"warning\": null,\n    \"vote\": true\n  },\n  \"agreement\": {\n    \"mode\": \"deep\",\n    \"n\": 5,\n    \"k\": 5,\n    \"decided\": true,\n    \"votes\": {\n      \"fault\": 0,\n      \"no_fault\": 5,\n      \"no_verdict\": 0\n    },\n    \"vote_rule\": \"decided when the majority verdict has k >= ceil(n/2) parsed votes; n = reads attempted, failed reads count in n and never in k; a tie is never decided; undecided re-votes once\",\n    \"re_voted\": false,\n    \"reported_round\": 1,\n    \"rounds\": [\n      {\n        \"round\": 1,\n        \"n\": 5,\n        \"reads_parsed\": 5,\n        \"read_failures\": [],\n        \"k\": 5,\n        \"decided\": true,\n        \"votes\": {\n          \"fault\": 0,\n          \"no_fault\": 5,\n          \"no_verdict\": 0\n        },\n        \"reason\": \"k >= ceil(n/2)\",\n        \"reported\": true\n      }\n    ],\n    \"reads_attempted_total\": 5,\n    \"majority_verdict\": false,\n    \"need\": 3,\n    \"verdict_field\": \"grounded fault_detected (contract R1 visible-in-photo + R3 taxonomy class and summary), majority per a2_selfcons_v11 over parsed reads\",\n    \"delivered_matches_majority\": true,\n    \"applicable\": true\n  },\n  \"callback_url\": \"/api/outcome?diagnosis_id=2a0a447e-9ef1-4ec2-bde0-46ef5e7088bb\",\n  \"callback\": {\n    \"method\": \"POST\",\n    \"url\": \"/api/outcome\",\n    \"note\": \"api/outcome.js reads diagnosis_id from the JSON BODY on POST; the query string on callback_url is there so the link identifies the diagnosis to a human and in logs, not because the endpoint reads it.\",\n    \"required_body\": {\n      \"diagnosis_id\": \"2a0a447e-9ef1-4ec2-bde0-46ef5e7088bb\",\n      \"outcome\": [\n        \"fixed\",\n        \"not_fixed\",\n        \"partial\"\n      ],\n      \"reported_via\": [\n        \"results_screen\",\n        \"scan_log\",\n        \"triage_link\"\n      ]\n    }\n  },\n  \"core\": {\n    \"is_building_asset\": true,\n    \"asset_type\": \"Gas Furnace and Air Handler\",\n    \"asset_category\": \"HVAC\",\n    \"photo_subject\": \"Gas furnace and air handler unit in mechanical room\",\n    \"report_photo_agreement\": null,\n    \"fault_detected\": false,\n    \"fault_visible_in_photo\": false,\n    \"fault_summary\": \"\",\n    \"fault_classes\": [],\n    \"severity\": \"P4\",\n    \"tier\": \"WHENEVER\",\n    \"criterion_1_fired\": false,\n    \"tier_forced_by\": null,\n    \"visible_wiring\": false,\n    \"water_near_electrical\": false,\n    \"safety_hazard\": false,\n    \"hazard_detail\": null,\n    \"trade_required\": null,\n    \"confidence\": 1,\n    \"resident_explanation\": \"Your HVAC unit is currently operating normally with no visible signs of damage, leaks, or electrical hazards.\"\n  },\n  \"extended\": {\n    \"isHighRisk\": false,\n    \"hazard_class\": \"H0\",\n    \"active_loss\": \"L0\",\n    \"habitability\": \"C0\",\n    \"deferral_cost\": \"D0\",\n    \"severity_reason_row\": 7,\n    \"make_model\": null,\n    \"identification_reasoning\": \"Furnace cabinet, evaporator coil plenum, and metallic ductwork clearly visible.\",\n    \"observations\": [\n      {\n        \"label\": \"Furnace\",\n        \"box\": [\n          676,\n          237,\n          1000,\n          824\n        ],\n        \"confidence\": 0.98\n      },\n      {\n        \"label\": \"Air Handler\",\n        \"box\": [\n          336,\n          264,\n          680,\n          796\n        ],\n        \"confidence\": 0.98\n      },\n      {\n        \"label\": \"Ductwork\",\n        \"box\": [\n          0,\n          0,\n          350,\n          999\n        ],\n        \"confidence\": 0.98\n      }\n    ],\n    \"loto_required\": false,\n    \"skill_level\": null,\n    \"diy_appropriate\": false,\n    \"estimated_duration_minutes\": 0,\n    \"parts_manifest\": [],\n    \"resident_why_it_matters\": \"Regular maintenance prevents unexpected breakdowns and extends equipment lifespan.\",\n    \"resident_do_not\": null,\n    \"resident_what_happens_next\": \"Continue normal operation and routine filter replacements.\",\n    \"root_cause\": null,\n    \"failure_mechanism\": null,\n    \"differential\": [],\n    \"wiring_water_reason\": \"none visible; none visible\"\n  },\n  \"contract_validation\": {\n    \"contract_version\": \"v1-DRAFT-2026-09-10\",\n    \"policy\": \"repair-and-record\",\n    \"policy_note\": \"A violation is REPAIRED and the repair is listed below. Repairs never invent a photo-grounded fact, a taxonomy class or prose; where the engine gave no support for an assertion the assertion is retracted and preserved under `retracted`. If any violation survives repair the whole response is refused (502 contract_unrepairable) rather than emitted.\",\n    \"rules_source\": {\n      \"file\": \"qa/engine-v2/CONTRACT-v1.json\",\n      \"rules_in_contract\": 8,\n      \"predicates_implemented\": 8,\n      \"drift\": [],\n      \"schema_fields_in_contract\": 51,\n      \"schema_keywords_enforced\": [\n        \"type\",\n        \"enum\",\n        \"pattern\",\n        \"minimum\",\n        \"maximum\",\n        \"minLength\",\n        \"minItems\",\n        \"maxItems\",\n        \"uniqueItems\",\n        \"items\",\n        \"properties\",\n        \"required\",\n        \"additionalProperties\"\n      ],\n      \"schema_keywords_ignored_as_annotation\": [\n        \"$schema\",\n        \"$id\",\n        \"title\",\n        \"description\",\n        \"format\"\n      ],\n      \"shape_drift\": []\n    },\n    \"scope_checked\": {\n      \"extended_requested\": true,\n      \"has_report\": false,\n      \"clauses_not_evaluated\": [],\n      \"core_fields_not_governed_by_contract\": [\n        \"tier\",\n        \"criterion_1_fired\",\n        \"tier_forced_by\",\n        \"visible_wiring\",\n        \"water_near_electrical\"\n      ],\n      \"contract_required_fields_this_door_does_not_emit\": [\n        \"category\",\n        \"diagnosis\",\n        \"difficulty\",\n        \"rootCause\",\n        \"materials\",\n        \"steps\",\n        \"proTip\",\n        \"estimatedDIYCost\",\n        \"estimatedProCost\",\n        \"youtubeVideoId\"\n      ],\n      \"contract_required_scope_note\": \"json_schema.required is the CONSUMER surface’s required set (the PWA render keys). This door emits core + extended, a different projection; the fields listed above are surfaces this door is not, and their absence is not a violation. Every contract-required field the door DOES emit is checked.\"\n    },\n    \"violations_found\": [],\n    \"shape_violations_found\": [],\n    \"contract_conflicts\": [],\n    \"repairs\": [],\n    \"exemptions\": [],\n    \"retracted\": {},\n    \"repairs_changed_severity\": false,\n    \"severity_before_repair\": \"P4\",\n    \"conformant\": true,\n    \"residual_violations\": [],\n    \"severity_after_repair\": \"P4\"\n  },\n  \"provenance\": {\n    \"contract_version\": \"v1-DRAFT-2026-09-10\",\n    \"models\": {\n      \"identification\": \"gemini-3.5-flash-lite\",\n      \"reasoning\": \"gemini-3.5-flash-lite\"\n    },\n    \"temperature\": 0.2,\n    \"prompt_hash\": \"b52b0e83c8d01ccb\",\n    \"prompt_base\": \"api/_engine-v2-fast.json system_prompt (sha256 93f5cb191f1c14d46ab33a003622fe94e32feb069e29c61fb69c3c0be70d3f56) + user_text — the run-2 a1_rubric_v11 export, held to the harness by qa/engine-v2-fast/check-export-drift.py; the resident report, when sent, rides in the user text (api/_engine-v2-fast.js USER_TEXT_WITH_REPORT, unmeasured variant)\",\n    \"agreement\": {\n      \"mode\": \"deep\",\n      \"n\": 5,\n      \"k\": 5,\n      \"decided\": true,\n      \"votes\": {\n        \"fault\": 0,\n        \"no_fault\": 5,\n        \"no_verdict\": 0\n      },\n      \"vote_rule\": \"decided when the majority verdict has k >= ceil(n/2) parsed votes; n = reads attempted, failed reads count in n and never in k; a tie is never decided; undecided re-votes once\",\n      \"re_voted\": false,\n      \"reported_round\": 1,\n      \"rounds\": [\n        {\n          \"round\": 1,\n          \"n\": 5,\n          \"reads_parsed\": 5,\n          \"read_failures\": [],\n          \"k\": 5,\n          \"decided\": true,\n          \"votes\": {\n            \"fault\": 0,\n            \"no_fault\": 5,\n            \"no_verdict\": 0\n          },\n          \"reason\": \"k >= ceil(n/2)\",\n          \"reported\": true\n        }\n      ],\n      \"reads_attempted_total\": 5,\n      \"majority_verdict\": false,\n      \"need\": 3,\n      \"verdict_field\": \"grounded fault_detected (contract R1 visible-in-photo + R3 taxonomy class and summary), majority per a2_selfcons_v11 over parsed reads\",\n      \"delivered_matches_majority\": true,\n      \"applicable\": true\n    },\n    \"prompt_addendum\": \"none — the RUBRIC v1.1 axes and criterion-1 facts are asked for by the exported system prompt and enforced by responseSchema (sha256 559265bb8d66ae4fbe98d756b0f574fb51a042997388c4b58ff630071a04991b); the model is never asked for severity or tier\",\n    \"latency_ms\": 4706,\n    \"photo_sha256\": \"a4232950f74c029d219eacff3d0f84ad86744c3c584b3c2fdafb49afbfeed913\",\n    \"exif_strip_version\": \"exif-strip-v3-2026-09-11\",\n    \"model_proposed_severity\": null,\n    \"severity_assigned_by\": \"server:RUBRIC-v1.1-2026-09-11 via a2_selfcons_v11 vote (assignSeverityV11 on the voted facts)\",\n    \"severity_overwritten\": false,\n    \"spend_today\": {\n      \"computed_usd\": 0.027553,\n      \"limit_usd\": 20,\n      \"limit_source\": \"default\",\n      \"basis\": \"computed_from_usage_tokens_x_declared_prices_not_metered; scope=this_warm_instance_only (in-process counter; cross-instance day spend is under-counted by up to the number of warm instances)\",\n      \"token_estimate\": true\n    },\n    \"vote\": {\n      \"candidate\": \"a1_rubric_v11\",\n      \"n\": 5,\n      \"rounds\": [\n        {\n          \"round\": 1,\n          \"n\": 5,\n          \"reads_parsed\": 5,\n          \"read_failures\": [],\n          \"k\": 5,\n          \"decided\": true,\n          \"votes\": {\n            \"fault\": 0,\n            \"no_fault\": 5,\n            \"no_verdict\": 0\n          },\n          \"reason\": \"k >= ceil(n/2)\",\n          \"reported\": true\n        }\n      ],\n      \"tally\": {\n        \"n_votes_parsed\": 5,\n        \"n_asked\": 5,\n        \"fault_detected\": {\n          \"true\": 0,\n          \"n\": 5,\n          \"voted\": false\n        },\n        \"is_building_asset\": {\n          \"true\": 5,\n          \"n\": 5,\n          \"voted\": true\n        },\n        \"n_agreeing\": 5,\n        \"base_pool\": \"raw-matching agreeing reads\",\n        \"rubric_axes_pool\": {\n          \"indices\": [\n            0,\n            1,\n            2,\n            3,\n            4\n          ],\n          \"rule\": \"all reads\"\n        },\n        \"fault_classes\": {\n          \"counts\": {},\n          \"voted\": []\n        },\n        \"safety_hazard\": {\n          \"true\": 0,\n          \"n\": 5,\n          \"voted\": false\n        },\n        \"fault_visible_in_photo\": {\n          \"true\": 0,\n          \"n\": 5,\n          \"voted\": false\n        },\n        \"rubric_axes\": {\n          \"hazard_class\": {\n            \"counts\": {\n              \"H0\": 5\n            },\n            \"voted\": \"H0\",\n            \"tie_rule\": \"outright\"\n          },\n          \"active_loss\": {\n            \"counts\": {\n              \"L0\": 5\n            },\n            \"voted\": \"L0\",\n            \"tie_rule\": \"outright\"\n          },\n          \"habitability\": {\n            \"counts\": {\n              \"C0\": 5\n            },\n            \"voted\": \"C0\",\n            \"tie_rule\": \"outright\"\n          },\n          \"deferral_cost\": {\n            \"counts\": {\n              \"D0\": 5\n            },\n            \"voted\": \"D0\",\n            \"tie_rule\": \"outright\"\n          }\n        },\n        \"criterion_1\": {\n          \"visible_wiring\": {\n            \"true\": 0,\n            \"n\": 5,\n            \"voted\": false,\n            \"tie_rule\": \"tie -> FALSE (v1.1 §2.1 uncertain does not fire)\"\n          },\n          \"water_near_electrical\": {\n            \"true\": 0,\n            \"n\": 5,\n            \"voted\": false,\n            \"tie_rule\": \"tie -> FALSE (v1.1 §2.1 uncertain does not fire)\"\n          }\n        },\n        \"base_vote_index\": 0,\n        \"fault_prose_cleared\": [],\n        \"confidence_agreement_fraction\": 1,\n        \"decision\": {\n          \"n\": 5,\n          \"k\": 5,\n          \"need\": 3,\n          \"decided\": true,\n          \"votes\": {\n            \"fault\": 0,\n            \"no_fault\": 5,\n            \"no_verdict\": 0\n          },\n          \"majority_verdict\": false,\n          \"reason\": \"k >= ceil(n/2)\"\n        }\n      },\n      \"rubric\": {\n        \"rubric_version\": \"v1.1-2026-09-11\",\n        \"rubric_v11_sha256\": \"f5968d73f21b1488da9fb25b1568ed7efdfa7cc888b4cec56b20c2b72a4ee951\",\n        \"rubric_axes\": {\n          \"hazard_class\": \"H0\",\n          \"active_loss\": \"L0\",\n          \"habitability\": \"C0\",\n          \"deferral_cost\": \"D0\",\n          \"hazard_reason\": \"none visible\",\n          \"active_loss_reason\": \"none visible\",\n          \"habitability_reason\": \"none visible\",\n          \"deferral_reason\": \"none visible\"\n        },\n        \"rubric_axes_invalid\": {},\n        \"criterion_1\": {\n          \"visible_wiring\": false,\n          \"water_near_electrical\": false,\n          \"visible_wiring_reason\": \"none visible\",\n          \"water_near_electrical_reason\": \"none visible\"\n        },\n        \"criterion_1_invalid\": {},\n        \"criterion_1_fired\": false,\n        \"criterion_1_facts_fired\": [],\n        \"severity_reason_row\": 7,\n        \"step\": \"2:v1-row-7\",\n        \"rubric_p_uncapped\": \"P4\",\n        \"severity_capped_by\": null,\n        \"tier_forced_by\": null,\n        \"illegal_p1_no_fault\": false,\n        \"severity_source\": \"assignSeverityV11 on VOTED facts (never voted, never model-emitted)\",\n        \"per_vote_severity\": [\n          \"P4\",\n          \"P4\",\n          \"P4\",\n          \"P4\",\n          \"P4\"\n        ],\n        \"severity_final\": \"P4\",\n        \"tier_final\": \"WHENEVER\"\n      },\n      \"per_read\": [\n        {\n          \"fault_detected\": false,\n          \"fault_visible_in_photo\": false,\n          \"fault_classes\": [],\n          \"severity\": \"P4\",\n          \"tier\": \"WHENEVER\",\n          \"criterion_1_fired\": false,\n          \"is_building_asset\": true,\n          \"finish_reason\": \"STOP\",\n          \"attempts\": 1,\n          \"latency_ms\": 4427,\n          \"usage\": {\n            \"prompt_tokens\": 9572,\n            \"output_tokens\": 1022\n          }\n        },\n        {\n          \"fault_detected\": false,\n          \"fault_visible_in_photo\": false,\n          \"fault_classes\": [],\n          \"severity\": \"P4\",\n          \"tier\": \"WHENEVER\",\n          \"criterion_1_fired\": false,\n          \"is_building_asset\": true,\n          \"finish_reason\": \"STOP\",\n          \"attempts\": 1,\n          \"latency_ms\": 4553,\n          \"usage\": {\n            \"prompt_tokens\": 9572,\n            \"output_tokens\": 995\n          }\n        },\n        {\n          \"fault_detected\": false,\n          \"fault_visible_in_photo\": false,\n          \"fault_classes\": [],\n          \"severity\": \"P4\",\n          \"tier\": \"WHENEVER\",\n          \"criterion_1_fired\": false,\n          \"is_building_asset\": true,\n          \"finish_reason\": \"STOP\",\n          \"attempts\": 1,\n          \"latency_ms\": 4253,\n          \"usage\": {\n            \"prompt_tokens\": 9572,\n            \"output_tokens\": 1046\n          }\n        },\n        {\n          \"fault_detected\": false,\n          \"fault_visible_in_photo\": false,\n          \"fault_classes\": [],\n          \"severity\": \"P4\",\n          \"tier\": \"WHENEVER\",\n          \"criterion_1_fired\": false,\n          \"is_building_asset\": true,\n          \"finish_reason\": \"STOP\",\n          \"attempts\": 1,\n          \"latency_ms\": 4673,\n          \"usage\": {\n            \"prompt_tokens\": 9572,\n            \"output_tokens\": 1077\n          }\n        },\n        {\n          \"fault_detected\": false,\n          \"fault_visible_in_photo\": false,\n          \"fault_classes\": [],\n          \"severity\": \"P4\",\n          \"tier\": \"WHENEVER\",\n          \"criterion_1_fired\": false,\n          \"is_building_asset\": true,\n          \"finish_reason\": \"STOP\",\n          \"attempts\": 1,\n          \"latency_ms\": 4268,\n          \"usage\": {\n            \"prompt_tokens\": 9572,\n            \"output_tokens\": 1138\n          }\n        }\n      ],\n      \"read_failures\": [],\n      \"confidence_definition\": \"agreement fraction on the fault verdict over the reads that parsed (a2_selfcons_v11) — a vote count, not a model number\"\n    }\n  },\n  \"stored\": {\n    \"written\": false,\n    \"degraded\": false,\n    \"reason\": \"supabase_not_configured\",\n    \"table\": \"diy_guest_scans\"\n  },\n  \"warnings\": []\n}"
        }
      ]
    },
    {
      "name": "Preflight (OPTIONS)",
      "description": "CORS preflight. Answers 204 with Access-Control-Allow-Origin: * and the allowed headers (Content-Type, x-triage-key, x-triage-fuse-limit).",
      "request": {
        "method": "OPTIONS",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/api/triage",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "api",
            "triage"
          ]
        }
      },
      "response": [
        {
          "name": "204 — preflight",
          "originalRequest": {
            "method": "OPTIONS",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/triage",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "triage"
              ]
            }
          },
          "status": "No Content",
          "code": 204,
          "_postman_previewlanguage": "text",
          "header": [
            {
              "key": "access-control-allow-origin",
              "value": "*"
            },
            {
              "key": "access-control-allow-methods",
              "value": "POST, OPTIONS"
            },
            {
              "key": "access-control-allow-headers",
              "value": "Content-Type, x-triage-key, x-triage-fuse-limit"
            },
            {
              "key": "cache-control",
              "value": "no-store"
            }
          ],
          "body": ""
        }
      ]
    },
    {
      "name": "GET is refused (405)",
      "description": "The endpoint takes POST only. The message is the handler's own.",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/api/triage",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "api",
            "triage"
          ]
        }
      },
      "response": [
        {
          "name": "405 method_not_allowed",
          "originalRequest": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/triage",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "triage"
              ]
            }
          },
          "status": "Method Not Allowed",
          "code": 405,
          "_postman_previewlanguage": "json",
          "header": [
            {
              "key": "content-type",
              "value": "application/json; charset=utf-8"
            }
          ],
          "body": "{\n  \"error\": \"method_not_allowed\",\n  \"message\": \"Method not allowed. POST a photo to this endpoint.\"\n}"
        }
      ]
    },
    {
      "name": "Report the outcome (callback_url)",
      "description": "When you find out what the thing actually was, POST it back. ON PRODUCTION THIS WRITE IS REAL: the endpoint takes no key, and an id it has never seen is recorded as a new outcome row against nothing — so `{{diagnosis_id}}` is empty until you set it to the `diagnosis_id` from a triage you made (the ids in the mock's examples belong to no stored triage). `diagnosis_id` is read from the JSON body — the query string on `callback_url` is there so the link identifies the triage to a human and in logs, not because the endpoint reads it. `outcome` is one of fixed | not_fixed | partial; `reported_via` is one of results_screen | scan_log | triage_link; optional `fixed_by` (steps | other_diy | pro | replaced), `trade`, `fix_description`, `parts_text`, `email`. The examples below were captured handler-direct through the handler code on main (35ba8bf5) with the database stubbed; the 200 shows the shape, its row id is the stub's, and the id it echoes (00000000-0000-4000-8000-000000000000) is the synthetic id the capture sent — never a stored triage.",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/api/outcome",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "api",
            "outcome"
          ]
        },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"diagnosis_id\": \"{{diagnosis_id}}\",\n  \"outcome\": \"fixed\",\n  \"reported_via\": \"triage_link\",\n  \"fixed_by\": \"pro\",\n  \"trade\": \"hvac\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      },
      "response": [
        {
          "name": "200 — recorded (handler-direct, database stubbed; the row id is the stub's)",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/outcome",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "outcome"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"diagnosis_id\": \"{{diagnosis_id}}\",\n  \"outcome\": \"fixed\",\n  \"reported_via\": \"triage_link\",\n  \"fixed_by\": \"pro\",\n  \"trade\": \"hvac\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "status": "OK",
          "code": 200,
          "_postman_previewlanguage": "json",
          "header": [
            {
              "key": "content-type",
              "value": "application/json; charset=utf-8"
            }
          ],
          "body": "{\n  \"ok\": true,\n  \"outcome\": {\n    \"id\": \"stub-row-1\",\n    \"diagnosis_id\": \"00000000-0000-4000-8000-000000000000\",\n    \"outcome\": \"fixed\",\n    \"fixed_by\": \"pro\",\n    \"reported_via\": \"triage_link\"\n  }\n}"
        },
        {
          "name": "400 outcome_orphan_refused — diagnosis_id missing or not a UUID",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/outcome",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "outcome"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"diagnosis_id\": \"not-a-uuid\",\n  \"outcome\": \"fixed\",\n  \"reported_via\": \"triage_link\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "status": "Bad Request",
          "code": 400,
          "_postman_previewlanguage": "json",
          "header": [
            {
              "key": "content-type",
              "value": "application/json; charset=utf-8"
            }
          ],
          "body": "{\n  \"error\": \"outcome_orphan_refused\",\n  \"detail\": \"diagnosis_id (uuid) is required\"\n}"
        },
        {
          "name": "400 outcome_label_invalid — outcome not one of fixed | not_fixed | partial",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/outcome",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "outcome"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"diagnosis_id\": \"{{diagnosis_id}}\",\n  \"outcome\": \"sorted\",\n  \"reported_via\": \"triage_link\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "status": "Bad Request",
          "code": 400,
          "_postman_previewlanguage": "json",
          "header": [
            {
              "key": "content-type",
              "value": "application/json; charset=utf-8"
            }
          ],
          "body": "{\n  \"error\": \"outcome_label_invalid\",\n  \"allowed\": [\n    \"fixed\",\n    \"not_fixed\",\n    \"partial\"\n  ]\n}"
        },
        {
          "name": "400 reported_via_invalid — reported_via not one of results_screen | scan_log | triage_link",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/outcome",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "outcome"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"diagnosis_id\": \"{{diagnosis_id}}\",\n  \"outcome\": \"fixed\",\n  \"reported_via\": \"pm_close\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "status": "Bad Request",
          "code": 400,
          "_postman_previewlanguage": "json",
          "header": [
            {
              "key": "content-type",
              "value": "application/json; charset=utf-8"
            }
          ],
          "body": "{\n  \"error\": \"reported_via_invalid\",\n  \"allowed\": [\n    \"results_screen\",\n    \"scan_log\",\n    \"triage_link\"\n  ]\n}"
        },
        {
          "name": "400 trade_invalid — trade not in the list",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/outcome",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "outcome"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"diagnosis_id\": \"{{diagnosis_id}}\",\n  \"outcome\": \"fixed\",\n  \"reported_via\": \"triage_link\",\n  \"trade\": \"locksmith\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "status": "Bad Request",
          "code": 400,
          "_postman_previewlanguage": "json",
          "header": [
            {
              "key": "content-type",
              "value": "application/json; charset=utf-8"
            }
          ],
          "body": "{\n  \"error\": \"trade_invalid\",\n  \"allowed\": [\n    \"plumber\",\n    \"electrician\",\n    \"hvac\",\n    \"appliance-repair\",\n    \"roofer\",\n    \"handyman\",\n    \"pest-control\",\n    \"landlord-maintenance\",\n    \"myself\",\n    \"other\"\n  ]\n}"
        },
        {
          "name": "409 triage_lane_migration_pending — the database has not applied the triage-lane migration (trade_reported column absent)",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/outcome",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "outcome"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"diagnosis_id\": \"{{diagnosis_id}}\",\n  \"outcome\": \"fixed\",\n  \"reported_via\": \"triage_link\",\n  \"trade\": \"hvac\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "status": "Conflict",
          "code": 409,
          "_postman_previewlanguage": "json",
          "header": [
            {
              "key": "content-type",
              "value": "application/json; charset=utf-8"
            }
          ],
          "body": "{\n  \"error\": \"triage_lane_migration_pending\",\n  \"detail\": \"public.outcomes does not yet accept this write. Apply qa/triage-loop/2026-09-11-outcomes-triage-lane.sql (RUSS GO), then retry.\",\n  \"migration\": \"qa/triage-loop/2026-09-11-outcomes-triage-lane.sql\",\n  \"db_code\": \"42703\"\n}"
        }
      ]
    }
  ]
}
