Search materials (query-string form)
The stable, authenticated search endpoint — GET query-string form. Provide `q` and/or `colours` and/or `mood`, OR `variantId` for a "looks like this material" seed search. At least one of `q`, `colours`, `mood`, or `variantId` is required. Enum params (`mood`/`sort`/`tol`/`textModes`) are validated against the shared search schema; an invalid value returns a 400 RFC 7807 problem listing the valid options in `suggestions`. Search is dynamic, so success responses carry `Cache-Control: no-store`. Requires the `mg:read` bearer scope.
Parameters
q | string | query | Free-text query, matched visually against material imagery. A bare colour word ("sage") automatically runs as a colour-only search. |
textModes | string | query | Comma-separated list of how to interpret `q`: `visual` (default) or `lexical` (BM25 attribute-lens search over names + canonical terms). Validated against the same enum as the JSON body's `textModes`; an unknown value returns a 400 listing the valid options in `suggestions`. |
colours | string | query | Comma-separated `HEX.WEIGHT` palette, weight optional, e.g. `9CAF88.6,E8E0D0.4`. Leading `#` optional on each hex. |
logic | enum | query | Cross-colour combination when `colours` is set. `all` (default) or `any`. |
mood | string | query | Mood-language colour entry — resolves to a curated palette. |
sort | enum | query | Re-order results. `relevance` (default) keeps the selected mode's primary score; `light`/`dark`/`vivid`/`muted`/`colourful` are the colour ramps; `name`/`name_desc`/`recent` are the catalogue orders — the same three `/search/browse` walks the corpus in. |
orderBy | string | query | Sort along ONE attribute's DECLARED value ladder — a SEPARATE axis from `sort`, which stays the closed relevance/colour/catalogue vocabulary (MG-1593). ONE JSON OBJECT: `orderBy={"attributeId":"price_tier","direction":"desc"}`; `direction` defaults to `asc`. Ordering is declared, never inferred from a value's spelling, so an attribute with no ladder is a 400 carrying `unorderable_attribute` and the full `orderable_attributes` set rather than a quiet fall back to relevance. Materials off the ladder, or carrying no value, sort LAST in both directions. Sent with `sort`, `orderBy` wins and the response `note` says so. |
returnAttributes | string | query | Comma-separated attribute ids (up to 8) whose resolved value rides each result under `attributes`, keyed by attribute id — so a page filtered on an attribute can display it without a second call per material. Three states, all different: no `attributes` key means it was not asked for, a key with `null` means nothing is on record, anything else is the value. An unknown id is a 400 naming it. |
tol | enum | query | How close a colour must be to count as a match. |
variantId | string | query | "Looks like this material" mode — seeds an image kNN from an existing variant. When set, `q`/`colours`/`mood` are ignored and the response additionally carries `seed` (a `getVariantBrief` echo of the seed variant). |
limit | integer | query | Results per page (default 24, max 48). |
explain | enum | query | Set to `1` to include per-signal scores (`signals`) on each result. |
count | enum | query | Set to `true` for an opt-in `counts.variants` feed total — EXACT up to a cap of 1,000 (`basis:"exact"`), `lowerBound` beyond the cap and always for ANN-ranked pools. Never silently approximated. |
sampleableOnly | enum | query | Require a recorded accepted sampleable=true fact before pagination. Unknown and false are excluded. False or absent leaves the corpus unchanged. |
hasImages | enum | query | Set to `true` to return only materials with at least one catalog image. Off by default so results stay honest about the full catalog (including image-less records); the `count` total honours it too. |
view | enum | query | `card` returns the lean grid projection (`searchCardItemSchema`: identity + name + thumbnail + score) instead of the full items; the response echoes `view`, which discriminates the `results` union. Default `full`. |
Response
oneOf
Projection echo — always 'full' on this envelope variant.
This exact hit's attribution key — `<responseId>:<rank>`, where rank is 1-based within THIS response. Unique across pages, because each page is its own response with its own id. Store it verbatim next to the outcome; it is opaque, and `attribution.responseId` is the field to group by.
The variant's Material Bank SKU — the ONE EXTERNAL identifier on a result, in contrast to the OPAQUE `variantId`: it is the id you are expected to match against Material Bank's own catalogue (carts, ordering and analytics all key on it), so a result list can be joined without a second lookup. Populated on EVERY lane — all four search arms and both Match lanes. ABSENT means no MB identity is known for the variant, never that the SKU cannot be ordered; presence is the orderability tell, absence is silence.
The governed manufacturer/source-native item code from `variants.code` (for example "GADLC02"). Populated when the full-result retrieval source carries that canonical field; Match always hydrates it from the variant row. ABSENT means unknown or an older external serving projection, never an invented code. Independent of materialBankId.
Explicit source-governed permission to save this exact source-coded variant to a Project. ABSENT means unknown/not allowed; it is never inferred from the brand or name.
Whether a physical sample is explicitly known to be orderable. Absent means unknown, never unavailable.
The governed data status supporting Project capture. ABSENT means unknown. Provisional data remains source-backed but is not a verified catalogue claim.
The material's main-image palette — the FULL stored palette, uncapped and in stored order. Colours are EXTRACTED FROM PRODUCT IMAGERY (visual likeness, never a declared colour specification). A one-swatch palette means the extractor found one colour — solid-colour materials are genuinely monochrome — never that the list was trimmed. This array is always present: EMPTY means nothing is on record (unknown), not 'this material has no colours'.
Present when this material's product imagery carries nothing beyond one colour, so a surface should render that colour rather than the photograph. Set for COATINGS (paints, primers, stains, floor coatings) whose stored palette quantises to a single swatch — a paint's photo is a flat fill, and the colour we hold structured beats a JPEG of it. ABSENT means the question was not established (unknown product type, unextracted or multi-swatch palette) and the image should be rendered; it NEVER asserts 'this is not a coating'.
Resolved attribute values for the ids sent in `returnAttributes`, keyed by attribute id. ABSENT entirely when `returnAttributes` was not sent. A key present with `null` means this variant carries no accepted value for that attribute — absent is not empty and neither is null: the three states are distinct on purpose. Values resolve with the normal variant-over-product precedence.
Match only (MG-1031). How close this material is to the query, as an ABSOLUTE image-space similarity in [0,1] adjusted for colour — the same unit as `qualityFloor` and `comparison.bestRejected`, so all three can be drawn on one axis. Never normalised against the page, so the number means the same thing across runs. It is the TRUE value and may reach 1; rendering it so that no card claims '100%' is the consumer's job (round toward zero, or show '99%+'). Capping it here would put it on a different ceiling from `bestRejected`, which publishes the same unit uncapped. Distinct from `score`, which is the selected mode's weighted primary ranking value on the `[0, maxScore]` scale. Absent outside Match.
`confidence` as a word, banded on the engine's measured readings (strong >= 0.85, good >= 0.72, fair >= 0.55 = the default quality floor, weak below it and reachable only under `includeBelowFloor`). Present exactly when `confidence` is.
stronggoodfairweakMatch only. Proportion-weighted OKLab distance from the query crop's palette to this material's. `null` means the material carries no stored palette — UNKNOWN colour, never 'no colour' and never 'distant'. Absent when the colour stage did not run.
`colourDistance` as a word (identical / very close / close / related / different), or null when unmeasurable. Absent when the colour stage did not run.
MB lists the SKU as not visible/retired; demoted in ranking. Omitted when false.
Present (true) only on quality-floor rejects returned under includeBelowFloor — the item failed the match image-quality floor and would be dropped by default.
Present (true) only on Match rows the colour probe admitted: this material is as close in colour as the catalogue gets, and its image similarity is BELOW the match quality floor by design. It is on the page because no image-retrieved row is nearer in colour, not because it cleared the floor — so a consumer can present it as a colour answer or suppress it knowingly, and a quality check can tell a designed admission from a genuine floor leak. Absent (never false) on rows that came through image retrieval. Distinct from `belowFloor`, which marks rejects returned only under `includeBelowFloor`.
Additional variants of the same product hidden by `collapse: "product"`. Present only when grouping hid at least one sibling. It is the COMPLETE tally, and stays so when `variants` is capped.
The product's matching variants, MOST RELEVANT FIRST — present exactly when `collapse: "product"` was requested, and then on every result. This is what makes the product-shaped view usable without a second call per card: the twelve colourways of one tile arrive with the product instead of being reduced to a number. Under the default variant grain (`collapse` absent or `"none"`) the key is ABSENT — every matching variant is already its own result, so there is no group to carry. A one-entry array means the product matched with exactly one variant, which is an answer rather than a gap. ORDER is the ranking pipeline's own and is never re-sorted: `variants[0]` is the group's RANKING representative — the highest-scoring member, whose score is the group's cursor key. If the display face differs from that representative, `explain` names those two identities apart in `anatomy`. CAPPED at 12 entries, with `siblingCount` left as the complete tally — so truncation is decidable from the response alone: `(siblingCount ?? 0) + 1 > variants.length`. A full-length array is never on its own evidence that the product holds no further variants.
Existing accepted/measured visual meanings — descriptive evidence, not ranking causes. Present only when `explain` was set and at least one meaning exists.
The shared report card for this hit against the request's `filters` (required cells) and `preferences` (preferred cells): one cell per criterion with its state (matched / unverified / failed / missing), value, per-cell trust, a grade, and the weakest confidence. With `verdicts: true` and a `similarTo: { variantId }` seed it ALSO carries `preferred` SEED cells (baseline `{ kind: "seed" }`) — one per comparable attribute the seed holds, plus a `colour` axis — `matched`/`deviated` with a directional deviation. Present (full view only) whenever a spec filter, preference, or seed verdict produced a cell.
How this hit was assembled — the ranking representative behind the displayed variant, and the siblings a product collapse hid. Present only when `explain` was set AND the hit actually holds one of those (a distinct representative, or at least one hidden sibling); a plain uncollapsed variant hit has no anatomy to report.
Result attribution receipt. Present on every successful ranked response. Nothing on our side retains it — what you store IS the record.
This response's stable id — the same value as the `X-Request-Id` header. Store it next to the outcome (an order, a save, a click) to join that outcome back to the ranking that produced it. Opaque: round-trip it verbatim, never parse it.
The ranking basis, groupable across both ranked routes. On /api/v1/search this is the envelope's `mode` verbatim; /api/v1/search/match reports 'match'. Group your stored outcomes by this field.
imagecolourtextlexicalentity-browsematchISO-8601 timestamp of when this response was assembled, so a stored outcome carries its own age without a second lookup.
Inspectable receipt for narrowing this exact finalized candidate pool. The token is opaque and never exposes variant ids.
How the ordinary search path interpreted the raw material-domain query: recognized senses, applied/refused/ambiguous outcomes, evidence, and the semantic text left after exact entity consumption. New standard-search responses include it; optional in the schema for rolling-deploy compatibility.
resolvedambiguouspartialunresolvedimagecolourtextlexicalentity-browseBounded structured refinements. Each request is a complete executable replacement for the current search request.
Human-readable description of the refinement.
Complete replacement search request. It can be parsed and executed directly; it is not a delta.
Present when the query has multiple valid readings — a colour⇄material word ('walnut'), a certification phrase wearing colour words ('red list free'), or a pinned paint chip that is also a colour to look for across materials, or an exact paint name shared by several makers. The reading that ran carries `isDefault`; for a refused paint collision every reading is false. Every reading is a complete executable request. ABSENT means the query was unambiguous, never that the alternative was checked and found empty.
Governed ambiguity class.
compliance-phrasecolour-materialpaint-colourpaint-identityThe ambiguous term or phrase.
Every valid reading. The one that ran is flagged `isDefault`; all are false when the engine intentionally chose none. Each is a complete executable request.
A mood term detected in the query but overridden by a stronger explicit colour reading. Absent means no mood was superseded, not that mood detection ran and found none.
warmcoolearthymoodyboldlight-airy+13 moreMood interpretation receipt. Present when the standard search evaluated mood; null ids explicitly mean no mood was selected. Absent means mood was not evaluated or the response came from an older server.
The original query text, or null without text.
Mood-language colour entry — resolves to a curated checked-in colour palette. Use it for evocative mood queries ('coastal', 'moody', 'earthy') rather than a single colour. A mood ALONE runs as a colour search; with text it remains diagnostic or hard-constraint evidence and does not blend into the text mode's ordering score. One of: warm (Warm — Reds, oranges, yellows - inviting and cozy); cool (Cool — Blues, teals, cool grays - calm and professional); earthy (Earthy — Browns, tans, terracotta - natural and grounded); moody (Moody — Deep, saturated, dramatic - rich and atmospheric); bold (Bold — High chroma, vibrant - energetic and eye-catching); light-airy (Light + Airy — Pastels, pale tones - soft and spacious); natural (Natural — Greens, wood tones, stone - organic and balanced); neutral (Neutral — Whites, grays, blacks - versatile and timeless); romantic (Romantic — Soft pinks, mauves, blush - tender and elegant); coastal (Coastal — Sandy beiges, ocean blues, seafoam - relaxed seaside); industrial (Industrial — Cool grays, concrete, metallic - urban and modern); luxe (Luxe — Deep jewel tones, rich emeralds, sapphires - opulent); vintage (Vintage — Muted, dusty tones - nostalgic and timeworn); serene (Serene — Soft blues, sage, muted lavender - calm and peaceful); scandi (Scandi — Warm whites, pale woods, cream - Scandinavian minimalism); cosy (Cosy — Deep plaster, amber, tobacco - enveloping warmth); heritage (Heritage — Rich historical tones with saturated depth - classic and grounded); mid-century (Mid-Century — Olive, mustard, teal, terracotta - retro modern); pastel (Pastel — Sage, duck egg, blush - soft colors with body).
warmcoolearthymoodyboldlight-airy+13 moreMood-language colour entry — resolves to a curated checked-in colour palette. Use it for evocative mood queries ('coastal', 'moody', 'earthy') rather than a single colour. A mood ALONE runs as a colour search; with text it remains diagnostic or hard-constraint evidence and does not blend into the text mode's ordering score. One of: warm (Warm — Reds, oranges, yellows - inviting and cozy); cool (Cool — Blues, teals, cool grays - calm and professional); earthy (Earthy — Browns, tans, terracotta - natural and grounded); moody (Moody — Deep, saturated, dramatic - rich and atmospheric); bold (Bold — High chroma, vibrant - energetic and eye-catching); light-airy (Light + Airy — Pastels, pale tones - soft and spacious); natural (Natural — Greens, wood tones, stone - organic and balanced); neutral (Neutral — Whites, grays, blacks - versatile and timeless); romantic (Romantic — Soft pinks, mauves, blush - tender and elegant); coastal (Coastal — Sandy beiges, ocean blues, seafoam - relaxed seaside); industrial (Industrial — Cool grays, concrete, metallic - urban and modern); luxe (Luxe — Deep jewel tones, rich emeralds, sapphires - opulent); vintage (Vintage — Muted, dusty tones - nostalgic and timeworn); serene (Serene — Soft blues, sage, muted lavender - calm and peaceful); scandi (Scandi — Warm whites, pale woods, cream - Scandinavian minimalism); cosy (Cosy — Deep plaster, amber, tobacco - enveloping warmth); heritage (Heritage — Rich historical tones with saturated depth - classic and grounded); mid-century (Mid-Century — Olive, mustard, teal, terracotta - retro modern); pastel (Pastel — Sage, duck egg, blush - soft colors with body).
warmcoolearthymoodyboldlight-airy+13 moreThe primary ranking source the query resolved to: 'image' (look-alike), 'colour' (the colour arm — pure), 'text' (visual text→image, the default), 'lexical' (BM25 over the attribute/name projection), or 'entity-browse' (the query names a brand and nothing else, so the brand's own catalogue in browse order is returned instead of a similarity ranking). A fully governed hard form + colour query filters the primary source's one candidate pool; that source still owns order and remains the reported mode.
imagecolourtextlexicalentity-browseWhich dense text lane answered: 'image-text' (the query carries evidence it describes appearance, so it is ranked against image vectors) or 'variant-text' (ranked against each variant's written portrait). Absent when the answer did not come from the dense text arm — absence is unknown, not a default.
image-textvariant-textokfailedskippedEligibility for a compound text colour: counts cover the bounded candidate pool, `adjacent` passed ColorScope hue compatibility, and missing colour evidence failed closed. Absent means colour was not a hard constraint.
explicitdetected-strongHard form eligibility for a compound text query. A candidate is kept only when governed size or body-type evidence supports the requested form; product and variant names are never proof.
subwayplanksquarehexagonpenny-roundFeed totals, present when `count:true` was requested OR when `collapse:"product"` grouped the results. `variants` is the total matching variants; `products` (only under `collapse`) the distinct product groups. Each total carries a `basis`: `exact` (true count, up to the cap of 1000) or `lowerBound` (a floor — the cap was hit, an ANN-ranked pool bounds it, or the count degraded). The lexical arm counts exactly via the tsvector predicate; a single accepted spec term reads the `mv_attribute_facet_counts` materialized view (refreshed post-ingest, so a hair stale by design); ANN pools (image / colour / visual-text) and `products` are always `lowerBound`. The two totals describe the same result set, so `products` never exceeds `variants`: both are measured against the pool this page was drawn from, and a `variants` total that came out below that pool is reported as the pool-bounded floor instead — it counted a narrower universe than the results beside it.
Per-query facet counts (MG-1010), present ONLY when the request sent `facetCounts`. Each requested facet's values are counted WITHIN this query's match set — a ranked query reports pool-derived floors (`basis:"lowerBound"`, `poolSize` stated), a filter-driven query true counts (`basis:"exact"`). See the `facetCounts` input field.
Present when the counts are POOL-DERIVED (every ranked query, and any filter-only query whose exact aggregation degraded): the size of the finalised candidate pool the per-value figures were counted over. Absent when the counts are exact under the query's whole predicate.
The facet ids the request asked for, echoed verbatim.
Requested facets that were not counted, each with its reason — `unsupported` (the id is known but not countable) or `timeout` (the facet stage's own budget expired; the results are unaffected). Empty when every requested facet was counted.
Per-facet value counts, keyed by the requested facet id. Every requested facet that is not in `absent` has an entry, including facets whose counted set held no values (`values: []`).
The corpus this page AND its `counts` were drawn from — present only when the request narrowed it. ABSENT means the whole graph. Read it with `counts`: under a scope a short page is a small SCOPED corpus, not a truncated one.
Which corpus to answer from. samplizeCatalogue admits published US paint colours regardless of stock. ABSENT (or 'all') means the WHOLE GRAPH — every material MaterialGraph knows, orderable or not; absence is never a narrower default. Three transaction-oriented scopes gate on governed CHANNEL membership: 'materialBankNaOrderable' (Material Bank North America, ~277,000 materials), 'materialBankEuOrderable' (Material Bank Europe, ~65,000) and 'designShopOrderable' (DesignShop, ~34,000, with the products DesignShop hides from browse excluded). 'materialBankNaAndCatalogue' is the one-query union of Material Bank North America and the governed Catalogue editorial discovery corpus; that union does not by itself claim every member is transactable. 'materialBankOrderable' is NOT one of them: it selects materials carrying a Material Bank identity, which is a provenance signal rather than a channel one, and it admits 98.4% of the graph — use it to exclude crawl-sourced brands, not to mean orderable. A scope answers membership in its named governed corpus, never suitability: scope on channel, then refine on sector. Every scope is applied INSIDE the query: `limit` fills with eligible rows and `counts` describe the scoped corpus, which post-filtering a page cannot give you. An unknown value is a 400 naming it, never a silent fall back to the whole graph.
allsamplizeCataloguematerialBankOrderablematerialBankNaOrderablematerialBankNaAndCataloguematerialBankEuOrderable+1 moreWhat the scope admits, in one sentence, for a human reader.
The `#rrggbb` colours the colour signal ACTUALLY ran on, after minor-proportion accents were dropped (MG-1549). Present whenever the request carried explicit `colours`. Compare against your request: a difference means the search that ran is not the search that was asked for.
Colours removed from the query for carrying a `weight` below the 5% accent threshold. EMPTY is the normal case. Non-empty and the dropped colours are absent from colour retrieval and ranking entirely — they did not merely rank low. To keep a small accent in the query, give it `role: "accent"`.
Engine-authored explanation for an empty page: the strongest observed origin plus retrieved, after-prune, and final counts. Present only under explain.
spec-allowlistbrand-scopeallowlist-truncatedretrieval-window-not-exhaustivesource-emptypost-retrieval-rejectedpagination-emptyno-authoritative-empty-proofLexical-projection coverage of substantive terms. Present only under explain.
covereduncoveredunavailableskippedThe single bounded candidate window that produced this page: versioned profile, requested depth, retrieval cost, and whether the selected source proved it was exhausted.
The executed query shape — result grain and the per-arm weight profile. Present only when `explain` is set. Diagnostic, not part of the ranking contract.
image-textvariant-textWhat one result stands for: "variant" (the default — every matching variant is its own result) or "product" (`collapse: "product"` grouped variants, so each result fronts a product and hides siblings).
variantproductThe applied ordering-signal profile. At most one weight is non-zero: the selected mode's primary signal. Multiplying that signal by its weight reproduces `score`, and the weight is `maxScore`. Other signals are diagnostic or hard-gate receipts and never blend.
The query-transformation stages that actually executed, in execution order. An applied stage changed execution; a skipped stage carries its reason. An empty array means this path ran no query transform.
Ordered request-understanding steps. Every entry says whether the step executed or skipped, why, and its measured wall-clock duration.
Present only when the query NAMED a canonical product type. See `applied` for whether it gated the candidate pool.
Present only when the query NAMED a brand the catalogue sells. See `applied` for whether it gated retrieval or was superseded by the empty-result retreat.
Present only when the brand stage saw a real brand and REFUSED to gate on it. Mutually exclusive with `brandAnchor`.
Per-stage wall-clock milliseconds (embed, retrieval, prune, constraints/enrichment, finalize, total). Present only when `explain` is set — diagnostic, not part of the ranking contract.
Echo of the resolved query input (image bytes/vector are never echoed).
A `getVariantBrief` echo of the seed variant — GET `?variantId=` mode only.
Projection echo — always 'card' on this envelope variant.
This exact hit's attribution key — `<responseId>:<rank>`, where rank is 1-based within THIS response. Unique across pages, because each page is its own response with its own id. Store it verbatim next to the outcome; it is opaque, and `attribution.responseId` is the field to group by.
Product name (the full item's `product`).
Whether a physical sample is explicitly known to be orderable. Absent means unknown, never unavailable.
Resolved attribute values for the ids sent in `returnAttributes`, keyed by attribute id. ABSENT entirely when `returnAttributes` was not sent. A key present with `null` means this variant carries no accepted value for that attribute — absent is not empty and neither is null: the three states are distinct on purpose. Values resolve with the normal variant-over-product precedence.
Result attribution receipt. Present on every successful ranked response. Nothing on our side retains it — what you store IS the record.
This response's stable id — the same value as the `X-Request-Id` header. Store it next to the outcome (an order, a save, a click) to join that outcome back to the ranking that produced it. Opaque: round-trip it verbatim, never parse it.
The ranking basis, groupable across both ranked routes. On /api/v1/search this is the envelope's `mode` verbatim; /api/v1/search/match reports 'match'. Group your stored outcomes by this field.
imagecolourtextlexicalentity-browsematchISO-8601 timestamp of when this response was assembled, so a stored outcome carries its own age without a second lookup.
Inspectable receipt for narrowing this exact finalized candidate pool. The token is opaque and never exposes variant ids.
How the ordinary search path interpreted the raw material-domain query: recognized senses, applied/refused/ambiguous outcomes, evidence, and the semantic text left after exact entity consumption. New standard-search responses include it; optional in the schema for rolling-deploy compatibility.
resolvedambiguouspartialunresolvedimagecolourtextlexicalentity-browseBounded structured refinements. Each request is a complete executable replacement for the current search request.
Human-readable description of the refinement.
Complete replacement search request. It can be parsed and executed directly; it is not a delta.
Present when the query has multiple valid readings — a colour⇄material word ('walnut'), a certification phrase wearing colour words ('red list free'), or a pinned paint chip that is also a colour to look for across materials, or an exact paint name shared by several makers. The reading that ran carries `isDefault`; for a refused paint collision every reading is false. Every reading is a complete executable request. ABSENT means the query was unambiguous, never that the alternative was checked and found empty.
Governed ambiguity class.
compliance-phrasecolour-materialpaint-colourpaint-identityThe ambiguous term or phrase.
Every valid reading. The one that ran is flagged `isDefault`; all are false when the engine intentionally chose none. Each is a complete executable request.
A mood term detected in the query but overridden by a stronger explicit colour reading. Absent means no mood was superseded, not that mood detection ran and found none.
warmcoolearthymoodyboldlight-airy+13 moreMood interpretation receipt. Present when the standard search evaluated mood; null ids explicitly mean no mood was selected. Absent means mood was not evaluated or the response came from an older server.
The original query text, or null without text.
Mood-language colour entry — resolves to a curated checked-in colour palette. Use it for evocative mood queries ('coastal', 'moody', 'earthy') rather than a single colour. A mood ALONE runs as a colour search; with text it remains diagnostic or hard-constraint evidence and does not blend into the text mode's ordering score. One of: warm (Warm — Reds, oranges, yellows - inviting and cozy); cool (Cool — Blues, teals, cool grays - calm and professional); earthy (Earthy — Browns, tans, terracotta - natural and grounded); moody (Moody — Deep, saturated, dramatic - rich and atmospheric); bold (Bold — High chroma, vibrant - energetic and eye-catching); light-airy (Light + Airy — Pastels, pale tones - soft and spacious); natural (Natural — Greens, wood tones, stone - organic and balanced); neutral (Neutral — Whites, grays, blacks - versatile and timeless); romantic (Romantic — Soft pinks, mauves, blush - tender and elegant); coastal (Coastal — Sandy beiges, ocean blues, seafoam - relaxed seaside); industrial (Industrial — Cool grays, concrete, metallic - urban and modern); luxe (Luxe — Deep jewel tones, rich emeralds, sapphires - opulent); vintage (Vintage — Muted, dusty tones - nostalgic and timeworn); serene (Serene — Soft blues, sage, muted lavender - calm and peaceful); scandi (Scandi — Warm whites, pale woods, cream - Scandinavian minimalism); cosy (Cosy — Deep plaster, amber, tobacco - enveloping warmth); heritage (Heritage — Rich historical tones with saturated depth - classic and grounded); mid-century (Mid-Century — Olive, mustard, teal, terracotta - retro modern); pastel (Pastel — Sage, duck egg, blush - soft colors with body).
warmcoolearthymoodyboldlight-airy+13 moreMood-language colour entry — resolves to a curated checked-in colour palette. Use it for evocative mood queries ('coastal', 'moody', 'earthy') rather than a single colour. A mood ALONE runs as a colour search; with text it remains diagnostic or hard-constraint evidence and does not blend into the text mode's ordering score. One of: warm (Warm — Reds, oranges, yellows - inviting and cozy); cool (Cool — Blues, teals, cool grays - calm and professional); earthy (Earthy — Browns, tans, terracotta - natural and grounded); moody (Moody — Deep, saturated, dramatic - rich and atmospheric); bold (Bold — High chroma, vibrant - energetic and eye-catching); light-airy (Light + Airy — Pastels, pale tones - soft and spacious); natural (Natural — Greens, wood tones, stone - organic and balanced); neutral (Neutral — Whites, grays, blacks - versatile and timeless); romantic (Romantic — Soft pinks, mauves, blush - tender and elegant); coastal (Coastal — Sandy beiges, ocean blues, seafoam - relaxed seaside); industrial (Industrial — Cool grays, concrete, metallic - urban and modern); luxe (Luxe — Deep jewel tones, rich emeralds, sapphires - opulent); vintage (Vintage — Muted, dusty tones - nostalgic and timeworn); serene (Serene — Soft blues, sage, muted lavender - calm and peaceful); scandi (Scandi — Warm whites, pale woods, cream - Scandinavian minimalism); cosy (Cosy — Deep plaster, amber, tobacco - enveloping warmth); heritage (Heritage — Rich historical tones with saturated depth - classic and grounded); mid-century (Mid-Century — Olive, mustard, teal, terracotta - retro modern); pastel (Pastel — Sage, duck egg, blush - soft colors with body).
warmcoolearthymoodyboldlight-airy+13 moreThe primary ranking source the query resolved to: 'image' (look-alike), 'colour' (the colour arm — pure), 'text' (visual text→image, the default), 'lexical' (BM25 over the attribute/name projection), or 'entity-browse' (the query names a brand and nothing else, so the brand's own catalogue in browse order is returned instead of a similarity ranking). A fully governed hard form + colour query filters the primary source's one candidate pool; that source still owns order and remains the reported mode.
imagecolourtextlexicalentity-browseWhich dense text lane answered: 'image-text' (the query carries evidence it describes appearance, so it is ranked against image vectors) or 'variant-text' (ranked against each variant's written portrait). Absent when the answer did not come from the dense text arm — absence is unknown, not a default.
image-textvariant-textokfailedskippedEligibility for a compound text colour: counts cover the bounded candidate pool, `adjacent` passed ColorScope hue compatibility, and missing colour evidence failed closed. Absent means colour was not a hard constraint.
explicitdetected-strongHard form eligibility for a compound text query. A candidate is kept only when governed size or body-type evidence supports the requested form; product and variant names are never proof.
subwayplanksquarehexagonpenny-roundFeed totals, present when `count:true` was requested OR when `collapse:"product"` grouped the results. `variants` is the total matching variants; `products` (only under `collapse`) the distinct product groups. Each total carries a `basis`: `exact` (true count, up to the cap of 1000) or `lowerBound` (a floor — the cap was hit, an ANN-ranked pool bounds it, or the count degraded). The lexical arm counts exactly via the tsvector predicate; a single accepted spec term reads the `mv_attribute_facet_counts` materialized view (refreshed post-ingest, so a hair stale by design); ANN pools (image / colour / visual-text) and `products` are always `lowerBound`. The two totals describe the same result set, so `products` never exceeds `variants`: both are measured against the pool this page was drawn from, and a `variants` total that came out below that pool is reported as the pool-bounded floor instead — it counted a narrower universe than the results beside it.
Per-query facet counts (MG-1010), present ONLY when the request sent `facetCounts`. Each requested facet's values are counted WITHIN this query's match set — a ranked query reports pool-derived floors (`basis:"lowerBound"`, `poolSize` stated), a filter-driven query true counts (`basis:"exact"`). See the `facetCounts` input field.
Present when the counts are POOL-DERIVED (every ranked query, and any filter-only query whose exact aggregation degraded): the size of the finalised candidate pool the per-value figures were counted over. Absent when the counts are exact under the query's whole predicate.
The facet ids the request asked for, echoed verbatim.
Requested facets that were not counted, each with its reason — `unsupported` (the id is known but not countable) or `timeout` (the facet stage's own budget expired; the results are unaffected). Empty when every requested facet was counted.
Per-facet value counts, keyed by the requested facet id. Every requested facet that is not in `absent` has an entry, including facets whose counted set held no values (`values: []`).
The corpus this page AND its `counts` were drawn from — present only when the request narrowed it. ABSENT means the whole graph. Read it with `counts`: under a scope a short page is a small SCOPED corpus, not a truncated one.
Which corpus to answer from. samplizeCatalogue admits published US paint colours regardless of stock. ABSENT (or 'all') means the WHOLE GRAPH — every material MaterialGraph knows, orderable or not; absence is never a narrower default. Three transaction-oriented scopes gate on governed CHANNEL membership: 'materialBankNaOrderable' (Material Bank North America, ~277,000 materials), 'materialBankEuOrderable' (Material Bank Europe, ~65,000) and 'designShopOrderable' (DesignShop, ~34,000, with the products DesignShop hides from browse excluded). 'materialBankNaAndCatalogue' is the one-query union of Material Bank North America and the governed Catalogue editorial discovery corpus; that union does not by itself claim every member is transactable. 'materialBankOrderable' is NOT one of them: it selects materials carrying a Material Bank identity, which is a provenance signal rather than a channel one, and it admits 98.4% of the graph — use it to exclude crawl-sourced brands, not to mean orderable. A scope answers membership in its named governed corpus, never suitability: scope on channel, then refine on sector. Every scope is applied INSIDE the query: `limit` fills with eligible rows and `counts` describe the scoped corpus, which post-filtering a page cannot give you. An unknown value is a 400 naming it, never a silent fall back to the whole graph.
allsamplizeCataloguematerialBankOrderablematerialBankNaOrderablematerialBankNaAndCataloguematerialBankEuOrderable+1 moreWhat the scope admits, in one sentence, for a human reader.
The `#rrggbb` colours the colour signal ACTUALLY ran on, after minor-proportion accents were dropped (MG-1549). Present whenever the request carried explicit `colours`. Compare against your request: a difference means the search that ran is not the search that was asked for.
Colours removed from the query for carrying a `weight` below the 5% accent threshold. EMPTY is the normal case. Non-empty and the dropped colours are absent from colour retrieval and ranking entirely — they did not merely rank low. To keep a small accent in the query, give it `role: "accent"`.
Engine-authored explanation for an empty page: the strongest observed origin plus retrieved, after-prune, and final counts. Present only under explain.
spec-allowlistbrand-scopeallowlist-truncatedretrieval-window-not-exhaustivesource-emptypost-retrieval-rejectedpagination-emptyno-authoritative-empty-proofLexical-projection coverage of substantive terms. Present only under explain.
covereduncoveredunavailableskippedThe single bounded candidate window that produced this page: versioned profile, requested depth, retrieval cost, and whether the selected source proved it was exhausted.
The executed query shape — result grain and the per-arm weight profile. Present only when `explain` is set. Diagnostic, not part of the ranking contract.
image-textvariant-textWhat one result stands for: "variant" (the default — every matching variant is its own result) or "product" (`collapse: "product"` grouped variants, so each result fronts a product and hides siblings).
variantproductThe applied ordering-signal profile. At most one weight is non-zero: the selected mode's primary signal. Multiplying that signal by its weight reproduces `score`, and the weight is `maxScore`. Other signals are diagnostic or hard-gate receipts and never blend.
The query-transformation stages that actually executed, in execution order. An applied stage changed execution; a skipped stage carries its reason. An empty array means this path ran no query transform.
Ordered request-understanding steps. Every entry says whether the step executed or skipped, why, and its measured wall-clock duration.
Present only when the query NAMED a canonical product type. See `applied` for whether it gated the candidate pool.
Present only when the query NAMED a brand the catalogue sells. See `applied` for whether it gated retrieval or was superseded by the empty-result retreat.
Present only when the brand stage saw a real brand and REFUSED to gate on it. Mutually exclusive with `brandAnchor`.
Per-stage wall-clock milliseconds (embed, retrieval, prune, constraints/enrichment, finalize, total). Present only when `explain` is set — diagnostic, not part of the ranking contract.
Echo of the resolved query input (image bytes/vector are never echoed).
A `getVariantBrief` echo of the seed variant — GET `?variantId=` mode only.
Example
curl 'https://beta.materialgraph.com/api/v1/search' \ -H "Authorization: Bearer $MG_API_KEY"