---
title: "Preview a refine's routing (the soft quote) — nothing is created"
method: POST
path: "/v1/quotes/{id}/preview"
---

# Preview a refine's routing (the soft quote) — nothing is created

`POST /v1/quotes/{id}/preview`

## Request

- Base URL: `https://agenticfabricationnetwork.ai`
- URL: `https://agenticfabricationnetwork.ai/v1/quotes/{id}/preview`
- Auth: HTTP bearer

The same body as POST /v1/quotes/{id}/refine, planned and NOT launched: how many distinct fabricators (and which process lanes) the network would ask for the refined part, the shell's exact expected_vendors rows, and its exclusion notes. No child quote, no vendor call, no supersede of in-flight work, no demand or corpus write — fire one per edit; commit with /refine at your sync point. Answers 200 (never 201). An empty body previews the quote's own re-fan. Validation and not-found errors match refine's.

## Path parameters

- `id` string, required

## Request body

- RefineRequest: 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).
  - `ship_to_zip` string: 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` string
  - `deadline` string, date, nullable: 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` string[], nullable: 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` boolean, nullable
  - `submersible` boolean, nullable
  - `high_temp` boolean, nullable: Must tolerate sustained heat (vendor heat-resistance claim)
  - `food_safe` boolean, nullable: Must be vendor-certified food safe
  - `max_budget_cents` integer, nullable: All-in budget cap, USD cents. On refine, explicit null CLEARS an inherited cap
  - `requirements` string[], nullable: 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` object[]: 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.
    - `name` string, required: What the dimension is, in the agent's words ("hole_diameter")
    - `value` number, required
    - `unit` 'mm' | 'in', required
    - `tolerance_mm` number: Required tolerance for this dimension, in mm (±)
    - `critical` boolean: Marked critical on the drawing (inspection-worthy)
    - `note` string
  - `reliability` object, nullable: 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.
    - `min_stars` number: Only vendors whose displayed rating is at least this — vendors with no reviews yet are excluded
    - `min_reviews` integer: Only vendors with at least this many verified-purchase reviews
    - `proven_spec_orders` integer: Only vendors with at least N completed orders for this exact process+material — "a vendor that prints PC reliably"
  - `spec` object: 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.
    - `material` string, nullable
    - `color` string, nullable
    - `finish` string, nullable: Finish delta; finish-capable lanes requote with it, printer lanes keep the color. null = remove.
    - `thickness_in` number, nullable: Sheet gauge delta, inches. null = back to the material's default gauge.
    - `bends` unknown
    - `tapping` unknown
    - `inserts` unknown
    - `quantities` integer[], nullable
  - `remove_requirements` string[]: 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` boolean: 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` string: 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` object: "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).
    - `key` 'recommended' | 'price' | 'unit_price' | 'total_price' | 'arrival' | 'rating': 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` 'asc' | 'desc': desc reverses the key's natural direction ("most expensive first")
    - `custom` string[]: Explicit offer-id order, first on top
    - `label` string: Short human wording clients display for a custom order ("most green first")

## Response `200`

Routing preview

- QuotePreviewResponse: The SOFT quote (POST /v1/quotes/{id}/preview): the deterministic routing half of a refine — intake sniff, process filter, health/pause gates, capability gate, envelope prune, expected-vendor sizing — run for the refined request and stopped BEFORE the shell. Nothing is created: no child quote, no vendor dispatch, no supersede epoch, no demand-ledger or corpus write, so a client may fire one per edit. expected_vendors are exactly the rows the real refine's shell would publish; notes are the shell's notes verbatim (every plan-time 'vendor <id> excluded: …' line included). An expectation, never a promise — lanes can still decline once asked.
  - `status` 'preview', required
  - `quote_id` string, required: The quote the preview was planned against (the refine's parent)
  - `process` 'fdm_print' | 'resin_print' | 'powder_print' | 'metal_print' | 'elastomer_print' | 'cnc' | 'sheetmetal' | 'laser_cut' | 'decal' | 'print' | 'apparel' | 'auto', required
  - `fabricators` integer, required: Distinct fabricators across every expected lane
  - `lanes` object[], required: Every candidate lane in routing order — zero-vendor lanes included
    - `process` string, required
    - `vendors` integer, required
    - `quotes` integer: Estimated quotes on the lane — one per shop for a stated material, a shop's declared material count for a defaults run. An expectation; real quotes replace it.
  - `expected_vendors` object[], required: Same rows as QuoteResponse.routing.expected_vendors would carry on the shell
  - `notes` string[], required
  - `spec_asked` object: The refined ask's articulated spec (same filter as QuoteResponse.spec_asked)

---

[API](https://skmtc.dev/agenticfabricationnetwork/apis/afn-agentic-fabrication-network.md) · [All operations](https://skmtc.dev/agenticfabricationnetwork/apis/afn-agentic-fabrication-network/llms.txt) · [OpenAPI document](https://skmtc.dev/agenticfabricationnetwork/apis/afn-agentic-fabrication-network/revisions/42c16babcd2b?raw)
