{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/agenticfabricationnetwork-ai/json-schema/agenticfabricationnetwork-ai-refine-request-schema.json",
  "title": "RefineRequest",
  "description": "Body of POST /v1/quotes/{id}/refine: any subset of Constraints fields (narrowing — merged into the parent's constraints) plus an optional `spec` delta (a spec CHANGE, not a narrowing: affected lanes REQUOTE with the new spec through warm vendor sessions). Empty body re-reads the quote's live state. GOING BACK (ALC-127): explicit `null` on any clearable constraint (deadline, max_budget_cents, the attribute flags, reliability, requirements, and the three filters) CLEARS the inherited value — the undo widening; `remove_requirements` takes back individual requirements terms; `undo_last_step: true` ALONE restores the previous step's stored spec + constraints exactly (the quote's `steps` array names recent steps, newest first).",
  "x-generated": "2026-11-01",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/agenticfabricationnetwork-ai-openapi.json#/components/schemas/RefineRequest",
  "type": "object",
  "properties": {
    "ship_to_zip": {
      "type": "string",
      "description": "Destination US ZIP code. Omit if the buyer never stated one — the network stamps its assumed central-US destination (prices stay all-in with shipping) and declares it (routing.ship_to_zip + routing.ship_to { zip, assumed: true })"
    },
    "ship_to_country": {
      "type": "string",
      "default": "US"
    },
    "deadline": {
      "type": [
        "string",
        "null"
      ],
      "format": "date",
      "description": "Latest acceptable delivery date; offers that miss it (and nothing else) move to near_misses with their real ETA — never into offers[]. On refine, explicit null CLEARS an inherited deadline (near-misses convert back); omission inherits it"
    },
    "processes": {
      "type": [
        "array",
        "null"
      ],
      "items": {
        "type": "string",
        "enum": [
          "fdm_print",
          "resin_print",
          "powder_print",
          "metal_print",
          "elastomer_print",
          "cnc",
          "sheetmetal",
          "laser_cut",
          "decal",
          "print",
          "apparel"
        ]
      },
      "minItems": 1,
      "description": "Process-lane filter: quote ONLY these processes (intersected with routing candidates). Absent = no filter; on refine the child inherits the parent's filter, and an explicit null CLEARS an inherited filter (\"show everything again\"). Narrowing by process is a constraint, never a fresh quote — warm vendor sessions survive it."
    },
    "outdoor_rated": {
      "type": [
        "boolean",
        "null"
      ]
    },
    "submersible": {
      "type": [
        "boolean",
        "null"
      ]
    },
    "high_temp": {
      "type": [
        "boolean",
        "null"
      ],
      "description": "Must tolerate sustained heat (vendor heat-resistance claim)"
    },
    "food_safe": {
      "type": [
        "boolean",
        "null"
      ],
      "description": "Must be vendor-certified food safe"
    },
    "max_budget_cents": {
      "type": [
        "integer",
        "null"
      ],
      "description": "All-in budget cap, USD cents. On refine, explicit null CLEARS an inherited cap"
    },
    "requirements": {
      "type": [
        "array",
        "null"
      ],
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 120
      },
      "maxItems": 20,
      "description": "Catch-all requirements channel: verbatim terms from the buyer's conversation (\"UV resistant\", \"anodized: red\", \"iso 13485\", \"tolerance: 0.1mm\"). Each term resolves deterministically — applied terms affect offers, unresolved terms come back in the quote's notes and are logged as demand for vendor sourcing. Terms never override explicit spec/constraint fields and are never an error; there is no wrong place to put a term."
    },
    "callouts": {
      "type": "array",
      "maxItems": 50,
      "items": {
        "type": "object",
        "required": [
          "name",
          "value",
          "unit"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "What the dimension is, in the agent's words (\"hole_diameter\")"
          },
          "value": {
            "type": "number"
          },
          "unit": {
            "type": "string",
            "enum": [
              "mm",
              "in"
            ]
          },
          "tolerance_mm": {
            "type": "number",
            "description": "Required tolerance for this dimension, in mm (±)"
          },
          "critical": {
            "type": "boolean",
            "description": "Marked critical on the drawing (inspection-worthy)"
          },
          "note": {
            "type": "string",
            "maxLength": 200
          }
        }
      },
      "description": "Drawing callouts the buyer's agent extracted — dimensions that matter. The tightest tolerance_mm prunes vendors whose published tolerance claim cannot hold it; vendors with no tolerance claim on record are excluded when a tolerance is demanded."
    },
    "reliability": {
      "type": [
        "object",
        "null"
      ],
      "description": "Vendor-reliability floor — narrows to vendors with a demonstrated track record. Like every constraint it narrows, never widens — except explicit null on a refine, which CLEARS the whole inherited floor.",
      "properties": {
        "min_stars": {
          "type": "number",
          "minimum": 1,
          "maximum": 5,
          "description": "Only vendors whose displayed rating is at least this — vendors with no reviews yet are excluded"
        },
        "min_reviews": {
          "type": "integer",
          "minimum": 1,
          "description": "Only vendors with at least this many verified-purchase reviews"
        },
        "proven_spec_orders": {
          "type": "integer",
          "minimum": 1,
          "description": "Only vendors with at least N completed orders for this exact process+material — \"a vendor that prints PC reliably\""
        }
      }
    },
    "spec": {
      "type": "object",
      "description": "Spec fields to CHANGE on the child quote — child spec = parent spec with these overridden. Material changes go HERE (\"I need it in PLA\" → {spec:{material:\"pla\"}}), NOT into requirements: an explicit material replaces the material fan-out. Free-form — unstocked asks resolve to the closest stocked material with a substitution note.",
      "properties": {
        "material": {
          "type": [
            "string",
            "null"
          ],
          "minLength": 1,
          "maxLength": 48
        },
        "color": {
          "type": [
            "string",
            "null"
          ]
        },
        "finish": {
          "type": [
            "string",
            "null"
          ],
          "minLength": 1,
          "maxLength": 48,
          "description": "Finish delta; finish-capable lanes requote with it, printer lanes keep the color. null = remove."
        },
        "thickness_in": {
          "type": [
            "number",
            "null"
          ],
          "exclusiveMinimum": 0,
          "maximum": 1,
          "description": "Sheet gauge delta, inches. null = back to the material's default gauge."
        },
        "bends": {
          "type": [
            "array",
            "null"
          ],
          "maxItems": 50,
          "description": "FULL replacement list of articulated bends (rows per /v1/ufp/spec.schema.json `bends`). null = remove every bend."
        },
        "tapping": {
          "type": [
            "array",
            "null"
          ],
          "maxItems": 200,
          "description": "FULL replacement list of tapped threads (rows per /v1/ufp/spec.schema.json `tapping`). null = remove every tap."
        },
        "inserts": {
          "type": [
            "array",
            "null"
          ],
          "maxItems": 200,
          "description": "FULL replacement list of hardware inserts (rows per /v1/ufp/spec.schema.json `inserts`). null = remove every insert."
        },
        "quantities": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer",
            "minimum": 1
          },
          "minItems": 1,
          "maxItems": 5
        }
      }
    },
    "remove_requirements": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 120
      },
      "maxItems": 20,
      "description": "Take back INDIVIDUAL requirements terms by normalized match — the removal channel (terms merge as a union, so re-stating the list can never drop one). Unknown terms are ignored, never an error."
    },
    "undo_last_step": {
      "type": "boolean",
      "description": "Go back exactly one refine step: the network restores the PARENT quote's stored spec + constraints verbatim as a new child — exact, never reconstructed. Must be the ONLY field in the body. An undo is an unlabeled refine: open comparison scenarios close. Undoing the original quote is rejected with honest wording."
    },
    "scenario": {
      "type": "string",
      "minLength": 1,
      "maxLength": 40,
      "description": "Comparison lane label (\"carbon steel\"). A labeled refine sits SIDE BY SIDE with the parent's other lanes instead of superseding them: same label replaces that scenario's in-flight work, different labels stream concurrently (max 4 distinct active labels per parent — a 5th is rejected listing the active ones), and a refine WITHOUT a scenario resets the baseline and supersedes every lane. Each labeled child echoes `scenario` on every read. Keep labels short and human."
    },
    "order_by": {
      "type": "object",
      "description": "\"Organize by\": how offers[] should PRESENT — pure presentation, never a re-price or a filter. An order_by-ONLY body re-orders the SAME quote in place (no child, no vendor calls, instant); the ordering persists across reads and streaming (new offers slot into the asked order) and is inherited by refine children until replaced or reset with {\"key\": \"recommended\"}. Exactly one of `key` (objective sort) or `custom` (an explicit id order the agent judged itself, e.g. \"most green first\" from the preview images — `label` required with it, unlisted offers land below the listed ones).",
      "properties": {
        "key": {
          "type": "string",
          "enum": [
            "recommended",
            "price",
            "unit_price",
            "total_price",
            "arrival",
            "rating"
          ],
          "description": "Objective ordering, natural direction: price/unit_price/total_price cheapest first, arrival soonest-possible first, rating best first (review count breaks ties), recommended = the default price+speed rank (also the reset value)."
        },
        "direction": {
          "type": "string",
          "enum": [
            "asc",
            "desc"
          ],
          "description": "desc reverses the key's natural direction (\"most expensive first\")"
        },
        "custom": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "minItems": 1,
          "maxItems": 200,
          "description": "Explicit offer-id order, first on top"
        },
        "label": {
          "type": "string",
          "minLength": 1,
          "maxLength": 60,
          "description": "Short human wording clients display for a custom order (\"most green first\")"
        }
      }
    }
  }
}