Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/VisualGraphxLLC/API-HUB/llms.txt

Use this file to discover all available pages before exploring further.

The pricing engine is a strategy-based system. When a quote request arrives, resolve_quote loads the product, reads product_type, instantiates the appropriate resolver, and returns a QuoteResult with a full cost breakdown. No markup is applied at this layer — the public quote endpoint exposes raw supplier cost, making it safe to call from frontend components without leaking business pricing rules. Two resolvers are implemented: TieredVariantResolver for apparel and FormulaResolver for print.

TieredVariantResolver (apparel)

Apparel pricing is determined by matching the requested quantity to a tier band in the variant_prices table. A variant_id is required — prices are stored per variant (color + size combination), not per product.

Tier selection logic

The resolver queries all variant_prices rows for the variant where:
quantity_min <= :qty
AND (quantity_max IS NULL OR quantity_max >= :qty)
When multiple rows match — for example, when a supplier provides both Net and MSRP prices for the same band — the resolver applies this priority:
Priorityprice_type
1 (highest)Net
2Sale
3MSRP
4 (lowest)Case
The highest-priority matching row wins. If no tier row matches, the resolver falls back to variant.base_price. If base_price is also absent, MissingPricingDataError is raised.

Apparel quote request

POST /api/pricing/quote
Content-Type: application/json

{
  "product_id": "a1b2c3d4-0000-0000-0000-000000000001",
  "variant_id": "v1000000-0000-0000-0000-000000000001",
  "qty": 36
}

Apparel quote response

{
  "unit_price": "5.98",
  "total": "215.28",
  "currency": "USD",
  "breakdown": {
    "base": "4.98",
    "tier_match": {
      "group": "Net",
      "qty_band": "12-71",
      "tier_price": "5.98"
    },
    "qty": 36,
    "fallback": false
  }
}
fallback: true in the breakdown means no tier row matched and base_price was used instead.

FormulaResolver (print)

Print pricing is area-based. The resolver reads dimension bounds and formula parameters from print_details, validates the requested width and height, then computes:
unit_price = base × width × height × area_factor
total      = unit_price × qty + setup_cost
Both width and height are required in the request body. Omitting either raises BoundsError (422).

Formula parameter source

Parameters are resolved in priority order:
  1. print_details.raw_payload.formula — an explicit formula dict stored during adapter hydration:
    { "base": "0.0095", "area_factor": "1.0", "base_setup": "25.00" }
    
  2. print_details.base_price_per_sq_unit — a single coefficient with area_factor = 1 and base_setup = 0 implied.
If neither is present, MissingPricingDataError is raised.

Bounds enforcement

Before the formula runs, _check_bounds validates the requested dimensions against the product’s min/max columns:
CheckError message
width < min_width"width {w} below minimum {min}"
width > max_width"width {w} above maximum {max}"
height < min_height"height {h} below minimum {min}"
height > max_height"height {h} above maximum {max}"
Null bounds are skipped (unconstrained).
POST /api/pricing/quote
Content-Type: application/json

{
  "product_id": "b2c3d4e5-0000-0000-0000-000000000002",
  "width": "36",
  "height": "48",
  "qty": 10
}
{
  "unit_price": "16.42",
  "total": "189.20",
  "currency": "USD",
  "breakdown": {
    "base": "0.0095",
    "area": "1728.00",
    "area_factor": "1.0",
    "option_multipliers": [],
    "setup_cost": "25.00",
    "qty": 10
  }
}
area = width × height (in square inches when size_unit = "in"). total includes setup_cost added once per job, not per unit.

Public quote endpoint

POST /api/pricing/quote

No authentication required. Returns base cost with no markup applied. Safe to call from storefronts and frontend components. Request body fields
FieldTypeRequiredNotes
product_idUUIDYes
variant_idUUIDApparel onlyRequired for product_type = "apparel"
widthDecimal (≥ 0)Print onlyRequired for product_type = "print"
heightDecimal (≥ 0)Print onlyRequired for product_type = "print"
qtyInteger (> 0)Yes
selected_attribute_idsUUID[]NoReserved for option multiplier support
Extra fields in the request body are rejected (extra = "forbid" on the Pydantic model). Response: QuoteResult
FieldTypeNotes
unit_priceDecimalRounded to 2 decimal places (ROUND_HALF_UP)
totalDecimalunit_price × qty for apparel; unit_price × qty + setup_cost for print
currencystringAlways "USD"
breakdownApparelBreakdown or PrintBreakdownSee resolver sections above

Customer quote endpoint (internal only)

POST /api/customers//pricing/quote

This endpoint is internal only. It requires the X-Ingest-Secret header — the same secret used for all n8n → FastAPI calls. Never expose this endpoint to end users or public storefronts without re-evaluating the auth boundary.
Returns the base price with customer-specific markup rules and storefront overrides applied. Only n8n workflows should call this endpoint. Request
POST /api/customers/c0ffee00-0000-0000-0000-000000000001/pricing/quote
X-Ingest-Secret: <your-ingest-secret>
Content-Type: application/json

{
  "product_id": "a1b2c3d4-0000-0000-0000-000000000001",
  "variant_id": "v1000000-0000-0000-0000-000000000001",
  "qty": 36
}
Response: CustomerQuoteResult Extends QuoteResult with markup metadata.
FieldTypeNotes
unit_priceDecimalPost-markup unit price
totalDecimalPost-markup total
base_unit_priceDecimalRaw supplier cost before markup
markup_pctDecimalMarkup percentage applied, if any
roundingstringRounding strategy applied (e.g. "nearest_cent")
storefront_override_appliedboolTrue if a per-storefront price override was used
{
  "unit_price": "7.18",
  "total": "258.46",
  "currency": "USD",
  "base_unit_price": "5.98",
  "markup_pct": "20.00",
  "rounding": "nearest_cent",
  "storefront_override_applied": false,
  "breakdown": {
    "base": "4.98",
    "tier_match": {
      "group": "Net",
      "qty_band": "12-71",
      "tier_price": "5.98"
    },
    "qty": 36,
    "fallback": false
  }
}

Live pricing in the frontend

The frontend uses a 250ms debounced hook (use-debounced-quote.ts) that fires POST /api/pricing/quote whenever the user changes quantity or print dimensions. This prevents flooding the API during rapid input.
  1. User selects a color and size → a variant_id becomes available.
  2. User changes the quantity field.
  3. After 250ms of inactivity, the hook sends a quote request with variant_id and qty.
  4. The response updates the displayed unit price and tier match badge in real time.

Error reference

Raised when a print product’s width or height falls outside the min_* / max_* bounds declared in print_details, or when width or height are omitted entirely for a print product.
{ "detail": "width 200.00 above maximum 144.00" }
Raised when required pricing data is absent. HTTP status depends on what is missing:
  • 404 — the product_id itself does not exist in the database.
  • 422 — the product exists but is missing a variant_id (apparel), has no variant_prices and no base_price, or has no print_details / formula (print).
{ "detail": "Variant v1000000-... has no variant_prices and no base_price" }

Rounding

All monetary values are rounded using ROUND_HALF_UP (not banker’s rounding) to 2 decimal places via the shared to_cents helper:
CENT = Decimal("0.01")

def to_cents(value: Decimal) -> Decimal:
    return Decimal(value).quantize(CENT, rounding=ROUND_HALF_UP)
This is applied to unit_price, total, base, setup_cost, and all breakdown fields. The area field in PrintBreakdown is not rounded — it preserves full precision as an intermediate value.

Build docs developers (and LLMs) love