Confirm correction

Submit this correction to MaterialGraph?

Developers

Read MaterialGraph over REST, MCP and the CLI.

MaterialGraph is a product graph for architectural materials: each brand has products, and each product has variants (one color, finish or size). Every variant carries attribute values such as color name, material and dimensions. The API searches and reads that graph.

Private beta. Not yet available; API keys on request.

First call

Check your key, then search.

Call the API from your backend. A bearer key is a secret and does not belong in client JavaScript. The buttons below send each request through our server with a read-only demo key, so you can see real responses without a key of your own.

1 · Check that the key is accepted: GET /api/v1/whoami

curl https://beta.materialgraph.com/api/v1/whoami \
  -H "Authorization: Bearer $MG_API_KEY"
Runs server-side with the demo key, so no key reaches your browser.

2 · Search with a few words. Results span every brand: POST /api/v1/variants/search

curl -X POST https://beta.materialgraph.com/api/v1/variants/search \
  -H "Authorization: Bearer $MG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "sage green wall tile",
    "limit": 5,
    "include": ["image"]
  }'
Runs server-side with the demo key, so no key reaches your browser.

3 · Read one variant by id, with the attributes you name: GET /api/v1/variants/{variantId}

curl "https://beta.materialgraph.com/api/v1/variants/6g23prj45moq6scfh2mn4?attributes=color_name" \
  -H "Authorization: Bearer $MG_API_KEY"

Authenticate

Send a key in the Authorization header.

Send your key as Authorization: Bearer or X-API-Key. A mg_read_ key grants the mg:read scope, which covers every endpoint on this page. There is no CORS, so browsers reach the API only through your own backend.

Key format

mg_<read|compose|source|ingest|write|admin>_…

The prefix names the scope tier. Higher tiers include mg:read.

Scopes by key tier

readmg:read
composemg:read, mg:compose
sourcemg:read, mg:source
ingestmg:read, mg:ingest, mg:compose, mg:source
writemg:read, mg:write
adminmg:read, mg:write, mg:admin, mg:ingest, mg:compose, mg:source

Get a key

Private beta. Keys are issued on request by the MaterialGraph team. Email a request and say which backend will use the key.

Client

Generate a typed client from the OpenAPI document.

The REST API is described by one OpenAPI 3.1 document. A generator such as openapi-generator can produce a client in your language from it.

generate
curl -O https://beta.materialgraph.com/openapi.yaml

npx @openapitools/openapi-generator-cli generate \
  -i openapi.yaml \
  -g typescript-fetch \
  -o ./mg-client

Reference

API reference

70 operations in 12 groups, each with parameters, request and response schemas, typed errors and a copy-paste curl. Generated from the OpenAPI document served at /openapi.yaml.

Color

GET/api/v1/colors/nearest-paintsmg:read

Nearest Samplize catalogue paints for several hexes

Normalises hexes to #RRGGBB and removes duplicates in input order. One indexed statement retrieves the nearest 100 settled paints per hex by OKLab cube KNN, within the eligible samplizeCatalogue subset including out-of-stock paints. ColorScope CIEDE2000 reorders those candidates and supplies the closeness bands. Cards, hex and LRV come from resolved_variants. Requires mg:read.

Parameters

hex
string[]queryRepeat for each six-digit hex, with or without #. Up to twelve before deduplication.
limit
integerqueryPaints per unique hex.

Response

200One result per unique canonical hex, in input order; paints ranked by CIEDE2000.
resultsobject[]
hexstring
paintsobject[]

Example

curl
curl 'https://beta.materialgraph.com/api/v1/colors/nearest-paints?hex=text' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/colors/profilesmg:read

Get the representative color palette of one or more categories

The colors a product category actually comes in, for up to 8 categories in ONE call, at either taxonomy tier. Each profile carries the category's display `name`/`path`, its total colored variants, its variant-weighted mean OKLab lightness and chroma, and its representative colors as hex + OKLab + variant count + proportion. `groupBy=group` accepts product-type GROUP keys and merges every leaf type under a group into one bucket histogram BEFORE ranking, so a group's palette is the group's palette rather than the union of its leaves' heads. `groupBy=merged` merges across the KEYS as well and returns a SINGLE profile over the union of their leaves — the palette of a caller-defined facet such as "Wood" (`wood_surface,wood_flooring`) that the taxonomy carries no single node for; its keys may sit at either tier, `categoryKey` is those keys sorted and joined with `+`, and `members` hands them back so nothing has to split that key. `select=diverse` runs a greedy max-min OKLab sampler (0.02 minimum separation, backfilled by frequency if fewer survive) so the swatches are visually spread — the one thing a client cannot reconstruct from a truncated frequency list. A category with no color data returns a zero-count profile, not a missing key. Counts are read live at request time from `resolved_variant_bands` (the dominant color of each searchable variant), so they are as current as the last write to a variant. This is a category-level aggregate, not a per-material read, so responses carry the shared vocabulary window, `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Parameters

productTypeIds
stringquery1–8 comma-separated category keys from `/api/v1/categories` — product-type ids, group ids when `groupBy=group`, or keys at either tier when `groupBy=merged`.
groupBy
enumqueryHow the keys map onto profiles. `type`/`group` name a taxonomy tier and return one profile per key, `group` merging each group's leaf product types into one palette. `merged` returns ONE profile over the union of every key's leaves.
topN
integerqueryRepresentative colors to keep per category. The ceiling leaves headroom for client-side diversity sampling over a frequency list.
select
enumquery`frequency` returns the most common colors; `diverse` returns the most visually distinct ones (0.02 minimum OKLab separation).

Response

200One color profile per requested category, in request order — or, under `groupBy=merged`, a single composite profile.
profilesobject[]

One profile per requested category, in request order — or, under groupBy=merged, a single composite profile.

avgChromanumber

Variant-weighted mean OKLab chroma across the category.

avgLightnessnumber

Variant-weighted mean OKLab lightness across the category.

categoryKeystring

The selected category's taxonomy key — a product type id, a group id under groupBy=group, or the sorted keys joined with "+" under groupBy=merged.

colorsobject[]

The category's representative colors, in the order the chosen `select` mode produced.

levelenum

Which taxonomy tier `categoryKey` names, or `merged` when it is a composite over a caller-defined key set.

grouptypemerged
membersstring[]

Under groupBy=merged, the caller's keys in composite-key order, so `categoryKey` never has to be split. Absent for single-category profiles.

namestring

Display name of the category, from the taxonomy.

pathstring

Display-name path, " / " separated, e.g. "Tile Surfaces / Ceramic and Porcelain Tile". A merged profile joins its members' paths with " + ".

variantCountinteger

Total colored variants behind this category.

Example

curl
curl 'https://beta.materialgraph.com/api/v1/colors/profiles?productTypeIds=text' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/colors/provenancemg:read

Get per-brand paint color provenance

Which paint brands publish a color value and which do not. Per brand: the paint variants MaterialGraph holds (`paintVariants`, the denominator), how many carry a color DECLARED by the manufacturer (`declared`), how many carry one DERIVED from product imagery (`derived`), how many carry neither, and `declaredCoverage` — the number that matters. The population is paint and coating product types only, because a declared color is not expected of a stone slab and counting its absence would be a slur rather than a measurement. `declared` and `derived` OVERLAP and are not a split: most declared paints also carry a derived query interpretation, and only `neither` is exclusive. There is deliberately no corpus average in the response — a single rate is the one shape that hides the finding, since a handful of publishing brands would mask every brand that publishes nothing. `totals` therefore carries counts, including `brandsPublishingNothing`. No query parameters. This is a corpus-level aggregate, not a per-material read, so responses carry the shared vocabulary window, `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Response

200One row per brand, largest paint population first, with corpus totals and the stated basis.
basisobject
declaredstring

What counts as a declared color value.

derivedstring

What counts as a derived color value.

omissionsstring[]

Questions this endpoint deliberately does NOT answer, and why — never silently absent.

populationstring

How the paint denominator was chosen, and from which tags.

brandsobject[]

One row per brand, largest paint population first.

brandstring

Brand display name, or `(unattributed)` for paint variants with no brand on record.

declaredinteger

Variants whose selected resolved palette contains a stated band.

declaredCoveragenumber

`declared / paintVariants`, 0–1. The number that matters.

derivedinteger

Variants whose selected resolved palette contains an isolated or crop band measured from imagery.

neitherinteger

Variants whose selected resolved palette contains neither a stated nor a measured band.

paintVariantsinteger

The denominator: variants whose PRIMARY accepted product type is a paint or coating type.

totalsobject
brandsinteger

Brands with at least one paint variant.

brandsPublishinginteger

Brands with at least one declared color value.

brandsPublishingNothinginteger

Brands whose declared count is zero — they publish nothing.

declaredinteger
derivedinteger
neitherinteger
paintVariantsinteger
variantsWithoutDeclaredinteger

`paintVariants - declared` — paint variants with no manufacturer-published color, including the partial gaps inside publishing brands.

Example

curl
curl 'https://beta.materialgraph.com/api/v1/colors/provenance' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/colors/consistencymg:read

Audit whether a brand's declared color hex agrees with its declared light reflectance

Compares each variant's DECLARED color hex against its DECLARED light reflectance value and reports the residual — `lrv` minus the sRGB relative luminance of the hex, both on 0–100. The instrument CANNOT say which side of a divergence is wrong: the hex, the LRV, the sheen (LRV is finish-specific), the observer/geometry mismatch between BS 8493 and sRGB, and legitimate gamut clipping all land in the same number. The signal is declared-color INCONSISTENCY, never "the LRV is wrong". Read `verdict` before any number. A brand whose LRV was COMPUTED from its hex piles every residual inside two-decimal rounding and would otherwise read as a clean bill of health for data carrying no independent information; two screens run first — the zero spike, and whether median |Δ| rises with chroma as optical error must — and such a brand comes back `circular` and MUST be excluded from any consistency claim. `verdict.reason` states which screen fired. `bands` splits the residuals into ≤1 / 1–5 / 5–20 / >20; a |Δ| of 1–5 is expected optical residual, not fault, and only |Δ| above `thresholds.defect` is a candidate defect. `brand` is optional and scopes the audit, because the corpus-wide join is the heavy one; omit it and `report` is null while `brands` still lists every auditable brand with its pair count, so no caller has to guess a name. `worst` bounds the offender list — a page of examples, never the raw pairs. `thresholds` carries the kernel's own constants so no client restates them. Requires the `mg:read` bearer scope.

Parameters

brand
stringqueryBrand display name, exactly as `brands[].brand` reports it. Omit to receive the auditable-brand index alone, with `report: null`.
worst
integerqueryHow many of the largest residuals to return in `report.worst`. Ignored when no `brand` is given.

Response

200The auditable-brand index, plus the audit for `brand` when one was given.
basisobject
instrumentstring
omissionsstring[]
populationstring
screensstring
brandsobject[]

Every brand holding declared hex + declared LRV pairs, most first. Counts only — always returned, so `brand` never has to be guessed.

brandstring

Brand display name, as `brand` accepts it.

pairsinteger

Variants of this brand carrying BOTH a declared color hex and a declared light reflectance value.

reportobject| null

The audit for `brand`, or null when no `brand` was asked for.

bandsobject

Residual counts per band. |Δ| of 1–5 is EXPECTED optical residual, not fault.

brandstring
defectsinteger

Pairs above `thresholds.defect` — the actionable count.

medianAbsDeltanumber| null
pairsinteger

Pairs measured for this brand, after the skips below.

skippedobject
verdictobject
worstobject[]

The largest residuals, bounded by `worst`. A page of examples, never the raw pairs.

thresholdsobject

The kernel's own constants, carried over the wire so no client restates them.

bandNotestring
bandsstring[]

The residual band ids, in order.

defectnumber
flatChromaTolerancenumber
minSamplesinteger
zeroSpikeCircularnumber
zeroSpikeEpsilonnumber

Example

curl
curl 'https://beta.materialgraph.com/api/v1/colors/consistency' \  -H "Authorization: Bearer $MG_API_KEY"

Variants

GET/api/v1/variants/{variantId}mg:read

Get one variant, compact, with only the detail asked for

Reads one variant by id: name, code, product, brand, category, image and page. Name canonical attribute or block ids in `attributes` and `blocks` (comma-separated) to add their values under `specifications`; `null` means unknown, never false or zero. Selectors the variant's product type lacks come back in `notApplicable`. Variant ids are opaque tokens: pass back exactly what a `list_variants` or `find_variants_by_vector` result gave you. One indexed statement reads the Resolved Variant with Brand and product joined by key. Ineligible and untyped variants still open by id; a superseded id returns its final surviving variant. The response includes eligibility, channels, the stored palette and selected attributes with their Value Source and exact wording. Success responses carry `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Parameters

variantId
stringpathThe variant id to resolve.
attributes
stringqueryComma-separated canonical attribute ids to return under `specifications`, e.g. `nrc_rating`. Absent: none.
blocks
stringqueryComma-separated canonical block ids to return whole, e.g. `acoustic`. Absent: none.
ifc
booleanquery`true` adds `ifc`: the IFC 4.3 Pset_ManufacturerTypeInformation properties and classification references.

Response

200The compact variant, with `specifications` and `notApplicable` present only when detail was asked for.
categoryobject| null

The first original source category, or null when unknown.

idstring
namestring
categoriesobject[]

All original source categories in source order.

idstring
namestring
productTypeobject| null

The accepted primary specification type, or null for a color sample or unresolved subject.

idstring
namestring
sampleSubjectenum
materialpaint_color_sampleunknownconflict
colorOptionValueIdstring| null
hasDeclaredColorboolean

True for a paint color card, paint line, primer or coating: the card is the brand's declared hex, never a photo.

codestring| null

The brand's item code.

hexstring| null

The settled card color hex, or null when unknown. Paint cards show this color and never a photo.

imageobject| null

The settled material card image. Always null for paint color samples and paint/coating product types.

altstring
urlstring
brandobject

The brand. `id` is the slug get_brand takes.

idstring
namestring
namestring

The variant's own name.

productobject
idstring
namestring
urlstring| null

The brand's page for it, or null when none is known.

variantIdstring
eligibleboolean

Whether lists may include this variant. Reads by id include ineligible variants.

channelsstring[]
paletteobject[]

The stored material palette, including how each band was obtained.

hexstring
sharenumber
dominantboolean
methodenum
statedisolatedcrop
attributesobject

Selected settled attributes with their Value Source and exact source wording.

materialBankIdstring

The variant's Material Bank SKU. Absent: none recorded.

notApplicableobject

Requested blocks or attributes this product type does not carry.

attributesstring[]
blocksstring[]
ifcobject

IFC 4.3: Pset_ManufacturerTypeInformation and IfcClassificationReference values. Absent properties are unknown.

propertySetsobject[]
classificationReferencesobject[]
specificationsobject
colorobject

Example

curl
curl 'https://beta.materialgraph.com/api/v1/variants/{variantId}' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/variants/{variantId}/assetsmg:read

List a variant's images and files

Returns one bounded, filtered, cursor-paged page of a variant's images and files: images first (primary image leading), then documents, then other files. `imageRoles` narrows only images and `documentTypes` narrows only documents, so each implies its own kind; asking for both, or pairing one with other kinds, is a 400 rather than a silently widened page. Raw PIM URLs and internal storage/provider metadata are excluded. A variant's assets change when an ingest lands, so success responses carry `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Parameters

variantId
stringpathThe variant id to resolve.
kinds
stringqueryComma-separated asset kinds to return; omit for every kind. One of: image, document, cad, bim, model_3d, texture, archive, other, unknown.
imageRoles
stringqueryComma-separated image roles (what an image shows); implies kinds=image. One of: product, swatch, detail, scene, technical, color_chart, sample_collection, other, unknown.
documentTypes
stringqueryComma-separated document purposes, any match; implies kinds=document. One of: technical_data_sheet, product_sheet, technical_bulletin, sustainability_report, specification, installation_guide, care_maintenance_guide, operation_manual, warranty, parts_list, technical_drawing, test_report, certificate, declaration_of_performance, environmental_product_declaration, health_product_declaration, ingredient_disclosure, safety_data_sheet, brochure, catalogue, color_finish_chart, other, unknown.
limit
integerqueryItems per page, 1..50.
cursor
stringquerypageInfo.nextCursor from the previous page, with the same variantId and filters.

Response

200One page of the variant's assets.
documentCoverageenum

Brand document list state. Absent when documents are filtered out.

availablenone_declarednot_ingestedunavailable_sourcedead_linkfailed_ingest
itemsobject[]
appliesTo"product"

Only 'product': a product-wide document. Absent: this variant's own.

assetIdstring
availabilityenum

available: MaterialGraph serves url. unknown: brand's link, unchecked.

availableunknownunavailable
byteSizeinteger

Not on images; absent when unknown.

documentTypesenum[]

Documents only; absent when unknown.

technical_data_sheetproduct_sheettechnical_bulletinsustainability_reportspecificationinstallation_guide+17 more
heightinteger

Images only; absent when unknown.

imageRoleenum

Images only; absent when unknown.

productswatchdetailscenetechnicalcolor_chart+3 more
kindenum
imagedocumentcadbimmodel_3dtexture+3 more
namestring

Not on images; absent when unknown.

presentationenum

Images only; absent when unknown.

edge_to_edgepackshotcutout_marginmontageunknown
urlstring

Absent when availability is unavailable.

widthinteger

Images only; absent when unknown.

pageInfoobject
nextCursorstring| null
returnedCountinteger
totalMatchesinteger
primaryAssetIdstring

The variant's primary image. Absent when it has none.

Example

curl
curl 'https://beta.materialgraph.com/api/v1/variants/{variantId}/assets' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/variantsmg:read

List variants by hard filter, ranked and cursor-paged

Eligible variants, hard-filtered by `filters`, ranked by `optionalFilters` and `orderBy`, cursor-paged. This is the successor to the retired `catalog_by_spec`/`search_browse`/materials-batch/siblings surface: a hard specification across every brand, a category or brand browse, a product's other colorways and a batch lookup by id are all a kind-grouped `filters` object now — `{terms, numbers, presence, anyField}`, every condition across all four ANDed (OR within one field is `in`). `filters`, `optionalFilters`, `orderBy` and `colors` are URL-encoded JSON; `attributes`, `include` and `words` are comma-separated lists; `limit`, `cursor` and `name` are plain values. `optionalFilters` RANK only — they never exclude, and each entry's `weight` (1–10, required) sums into `preference.score` on a matched item. Responses carry `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Parameters

filters
stringqueryURL-encoded JSON `{terms, numbers, presence, anyField}` — every condition across all four arrays (at most 20 in total) MUST match (AND); OR within one field is `in`. `terms` items are `{field, op, values}` (`in`/`all`/`not_in`); on an `in` or `all` over a term attribute, `orText` (1–100 characters) also meets the condition when that phrase appears in the variant's text, and on a `not_in`, `unstated: "pass"` lets through variants that do not state the attribute at all. `numbers` items are `{field, op, value}` (`eq`/`gt`/`gte`/`lt`/`lte`, in the attribute's own canonical unit); `presence` items are `{field, op}` (`exists`/`missing`); `anyField` items are `{fields, op, values}` (`in`/`not_in`, 2–8 term attributes) for one concept stored under several of them, and take `orText` and `unstated` too. `field` is an attribute id, or one of the reserved fields `variant`, `product`, `brand`, `category`, `product_type`, `lifecycle`, `certification_programme`.
name
stringqueryFind variants by name (MG-2222): every word must start a word of the product or variant name. Combines with `filters` (brand, category, and the rest) as an additional AND condition. Ambiguous matches are all returned rather than guessed between.
optionalFilters
stringqueryURL-encoded JSON, the same `{terms, numbers, presence, anyField}` shape as `filters` (with `orText` but not `unstated`), each item also carrying a required `weight` (1–10). Ranks matched items by summed weight; never excludes a non-matching one.
orderBy
stringqueryURL-encoded JSON array of `{field, direction: "asc" | "desc"}`, `field` a numeric attribute id.
colors
stringqueryURL-encoded JSON array of up to four measured-color regions, each `{hex, op: "in" | "not_in", intent?: "required" | "preferred", range: {lightness: [min, max], chroma: [min, max], hue: [start, end] | null}}`. `hex` is `#rrggbb`, the color's own point in the region; lightness and chroma bounds are 0..1; hue is an arc in degrees that wraps when its start is past its end, null for any hue. `in` keeps variants with a large swatch inside the region; `not_in` leaves those out and lets a variant with no measured color pass; `preferred` only orders and never narrows.
words
stringqueryComma-separated words each variant must name in its text, applied inside the filters.
attributes
stringqueryComma-separated attribute ids to project under each item's `specifications`, up to 20.
include
stringqueryComma-separated item fields to add: `image`, `url`. Absent by default — the item is compact on purpose, since a page repeats it up to 50 times.
searchCatalogueSubset
enumqueryNarrows the page and the cursor to one search catalogue subset, the same vocabulary search and brands take. `materialBankOrderable` keeps variants carrying a Material Bank id. Absent or `all` is every eligible variant.
collapse
enumquery`product` pages products rather than variants: each row is the product's lead colorway by the page's own order, and `variantCount` says how many colorways stand behind it. `colorway` pages colorways: a color sold on several substrates is one row. Absent: one row per variant.
limit
integerqueryItems per page, 1..50. Defaults to 10.
cursor
stringqueryOpaque continuation from the previous page's `pageInfo.endCursor`, bound to the same `filters`/`optionalFilters`/`orderBy`/`searchCatalogueSubset`/`collapse`. A cursor from other criteria is a 400 (`INVALID_CURSOR`).

Response

200One page of matching variants, with `unresolvedVariantIds` present only when the root `filters` named explicit `variant in` ids that name no variant, `unavailableVariantIds` only when some named a catalogue variant this list cannot show (retired, not orderable, or outside `searchCatalogueSubset`), and `excludedBySearchCatalogueSubset` only when some named a real variant outside the materials catalogue.
excludedBySearchCatalogueSubsetstring[]

Filtered variant ids outside the materials catalogue.

itemsobject[]
categoryobject| null

The first original source category, or null when unknown.

categoriesobject[]

All original source categories in source order.

productTypeobject| null

The accepted primary specification type, or null for a color sample or unresolved subject.

sampleSubjectenum
materialpaint_color_sampleunknownconflict
colorOptionValueIdstring| null
hasDeclaredColorboolean

True for a paint color card, paint line, primer or coating: the card is the brand's declared hex, never a photo.

codestring| null

The brand's item code.

hexstring| null

The settled card color hex, or null when unknown. Paint cards show this color and never a photo.

imageobject| null

The settled material card image. Always null for paint color samples and paint/coating product types.

brandobject

The brand. `id` is the slug get_brand takes.

namestring

The variant's own name.

productobject
urlstring| null

The brand's page for it, or null when none is known.

variantIdstring
materialBankIdstring

The variant's Material Bank SKU. Absent: none recorded.

notApplicableobject

Requested blocks or attributes this product type does not carry.

preferenceobject
specificationsobject
variantCountinteger

Eligible variants of this product. Absent: none counted.

nameWordsobject[]

Empty name lookup: each word, and whether a name in scope starts it. Unmatched words were left out unless all or none matched.

matchedboolean
wordstring
pageInfoobject
endCursorstring| null
hasNextPageboolean
preferenceMatchesRemainboolean

False: nothing from this page on meets any optionalFilter.

unavailableVariantIdsstring[]

Filtered ids in the catalogue this list can't show, e.g. retired.

unresolvedVariantIdsstring[]
unstatedstring[]

Filtered attributes no variant in scope states at all.

wordsobject[]

Each word asked for, and whether any variant in scope names it.

matchedboolean
wordstring

Example

curl
curl 'https://beta.materialgraph.com/api/v1/variants' \  -H "Authorization: Bearer $MG_API_KEY"
POST/api/v1/variants/facetsmg:read

Count variants per attribute value under a filter tree

Contextual facet values and exact variant counts over the eligible variants `filters` admits: the options a filter UI shows next to a `list_variants` page. `filters` is the same kind-grouped `{terms, numbers, presence, anyField}` tree `list_variants` takes, less its `orText` and `unstated` additions, validated by the same code. `attributeIds` names 1–10 facetable attributes (see `list_attributes`). A categorical or boolean facet returns its top `valuesPerFacet` values (default 10) with `hasMoreValues`; a numeric facet returns its observed `min`/`max`. `missingVariantCount` counts variants recording no value: unknown, never false or zero. Requires the `mg:read` bearer scope.

Request body

attributeIdsstring[]

1-10 facetable attribute ids or brand.

searchCatalogueSubsetenum

Count only this scope.

allsamplizeCataloguematerialBankOrderablematerialBankNaOrderablematerialBankNaAndCataloguematerialBankEuOrderable+1 more
filtersobject

AND conditions. field: attribute id, variant, product, brand, category, product_type, lifecycle.

anyFieldobject[]

One of values in any of fields (term attributes).

numbersobject[]
presenceobject[]
termsobject[]
valuesPerFacetinteger

Top values per categorical/boolean facet. Default 10.

Response

200`matchedVariantCount` eligible variants under `filters`, and one facet per requested attribute, in request order.
matchedVariantCountinteger
facetsvariant[]
kind: categoricalobject
attributeIdstring
namestring
missingVariantCountinteger
kind"categorical"
valuesobject[]
hasMoreValuesboolean
kind: booleanobject
attributeIdstring
namestring
missingVariantCountinteger
kind"boolean"
valuesobject[]
hasMoreValuesboolean
kind: numericobject
attributeIdstring
namestring
missingVariantCountinteger
kind"numeric"
minnumber| null
maxnumber| null
unitIdstring| null

Example

curl
curl -X POST 'https://beta.materialgraph.com/api/v1/variants/facets' \  -H "Authorization: Bearer $MG_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "attributeIds": [    "…"  ]}'
POST/api/v1/variants/searchmg:read

Find variants from plain words

Reads `query` into filters, runs them through `list_variants` and answers with what was understood beside a page of variants. `understood` says what each phrase became: a hard `filter`, a ranking `preference`, or `not_applied` when nothing could serve it. `filters`, `optionalFilters` and `name` are exactly what ran, so the three can be sent to `list_variants` to narrow or widen the same search. A phrase with more than one meaning is answered with one and listed in `choices`: send its `handle` with an alternative's `group` and `id` in `selections` to switch it. `unresolved` lists words nothing in the vocabulary could read. A query that applies nothing returns no items, never the whole catalogue; a preference alone still ranks it. `cursor` is the previous page's `pageInfo.endCursor`, tied to the vocabulary version the query was read against; it pages this operation only, not `list_variants`. Requires the `mg:read` bearer scope.

Request body

attributesstring[]

Attribute/block ids to add; id fields always return.

cursorstring
leaveOutstring[]
limitinteger

5-10 for examples.

querystring

What to find, in plain words.

selectionsobject[]

Switch a choice: handle, alternative group and id.

groupstring
handlestring
idstring
searchCatalogueSubsetenum

Only this corpus scope. Absent or all: every variant.

allsamplizeCataloguematerialBankOrderablematerialBankNaOrderablematerialBankNaAndCataloguematerialBankEuOrderable+1 more
includeenum[]
imageurl

Response

200One page of variants with the query interpretation beside it: `understood` always, and `filters`, `optionalFilters`, `name`, `choices` and `unresolved` when the query produced them.
excludedBySearchCatalogueSubsetstring[]

Filtered variant ids outside the materials catalogue.

itemsobject[]
categoryobject| null

The first original source category, or null when unknown.

categoriesobject[]

All original source categories in source order.

productTypeobject| null

The accepted primary specification type, or null for a color sample or unresolved subject.

sampleSubjectenum
materialpaint_color_sampleunknownconflict
colorOptionValueIdstring| null
hasDeclaredColorboolean

True for a paint color card, paint line, primer or coating: the card is the brand's declared hex, never a photo.

codestring| null

The brand's item code.

hexstring| null

The settled card color hex, or null when unknown. Paint cards show this color and never a photo.

imageobject| null

The settled material card image. Always null for paint color samples and paint/coating product types.

brandobject

The brand. `id` is the slug get_brand takes.

namestring

The variant's own name.

productobject
urlstring| null

The brand's page for it, or null when none is known.

variantIdstring
materialBankIdstring

The variant's Material Bank SKU. Absent: none recorded.

notApplicableobject

Requested blocks or attributes this product type does not carry.

preferenceobject
specificationsobject
variantCountinteger

Eligible variants of this product. Absent: none counted.

nameWordsobject[]

Empty name lookup: each word, and whether a name in scope starts it. Unmatched words were left out unless all or none matched.

matchedboolean
wordstring
pageInfoobject
endCursorstring| null
hasNextPageboolean
preferenceMatchesRemainboolean

False: nothing from this page on meets any optionalFilter.

unavailableVariantIdsstring[]

Filtered ids in the catalogue this list can't show, e.g. retired.

unresolvedVariantIdsstring[]
unstatedstring[]

Filtered attributes no variant in scope states at all.

wordsobject[]

Each word asked for, and whether any variant in scope names it.

matchedboolean
wordstring
choicesobject[]
alternativesobject[]
basisstring
handlestring
reasonstring
selectedobject
textstring
colorsobject[]

The color regions that ran.

hexstring

The color's own point in the region; it orders matches.

intentenum
requiredpreferred
openum
innot_in
rangeobject
correctionsobject[]
correctionstring
wordstring
filtersobject

The filters that ran, the scope among them.

anyFieldobject[]

One of values in any of fields (term attributes).

numbersobject[]
presenceobject[]
termsobject[]
namestring

The paint name looked up.

oneRowPerenum

A row per product, per variant, or per exact paint variant followed by other products.

productvariantpaint_variant_or_product
optionalFiltersobject

The preferences that ran.

anyFieldobject[]
numbersobject[]
presenceobject[]
termsobject[]
outcomeenum

How the search ended.

pagepartial_matchno_matchnothing_understoodout_of_scopebrand_not_in_catalogue
scopeobject

The fixed scope, repeated in filters.

anyFieldobject[]

One of values in any of fields (term attributes).

numbersobject[]
presenceobject[]
termsobject[]
understoodobject[]
appliedenum

filter narrows; preference only ranks; not_applied did neither.

filterpreferencenot_applied
idstring

The parser's condition id.

kindenum
categorybrandtermnumbercolorpaint+1 more
labelstring

What the words were read as.

notestring
textstring

The query words it came from.

unresolvedstring[]

Example

curl
curl -X POST 'https://beta.materialgraph.com/api/v1/variants/search' \  -H "Authorization: Bearer $MG_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "query": "…"}'
GET/api/v1/variants/by-colormg:read

Find variants by color — contains, palette or rerank

Flat query encoding: zero-based `color0` through `color5`, each with optional `.share`, `.shareMax`, `.dominant` and `.role` fields, e.g. `color0=%23aabbcc&color0.share=0.6`. Lists use repeated query keys; image fields are `imageAssetId`, `imageSourceId`, `imageUrl`, `imageVariantId`. The canonical color-discovery operation, one required `mode`: `contains` (variants holding a swatch for every REQUIRED `colors[]` condition, ordered by preferred conditions held then ColorVec cosine), `palette` (variants whose FULL palette resembles `colors[]`'s target shares, an image's extracted palette, or `targetVariantId`'s own palette, ordered by ColorVec cosine) or `rerank` (reorder a caller-held `variantIds` set by color closeness to `colors[0]` or `targetVariantId`). A color that fails to resolve is reported in `unresolvedColors` with actionable alternatives, never guessed. CONTAINS/PALETTE candidates are gated through `list_variants`' own eligibility and hard filters first. The page is a cursor over that gated, ranked pool, bound to the exact request that minted it — a cursor from different criteria is refused. Requires the `mg:read` bearer scope.

Parameters

color0
stringquery
color0.share
numberquery
color0.shareMax
numberquery
color0.dominant
enumquery
color0.role
enumqueryDefault required.
color1
stringquery
color1.share
numberquery
color1.shareMax
numberquery
color1.dominant
enumquery
color1.role
enumqueryDefault required.
color2
stringquery
color2.share
numberquery
color2.shareMax
numberquery
color2.dominant
enumquery
color2.role
enumqueryDefault required.
color3
stringquery
color3.share
numberquery
color3.shareMax
numberquery
color3.dominant
enumquery
color3.role
enumqueryDefault required.
color4
stringquery
color4.share
numberquery
color4.shareMax
numberquery
color4.dominant
enumquery
color4.role
enumqueryDefault required.
color5
stringquery
color5.share
numberquery
color5.shareMax
numberquery
color5.dominant
enumquery
color5.role
enumqueryDefault required.
brands
string[]query
categoryIds
string[]query
searchCatalogueSubset
enumqueryOnly this corpus scope.
cursor
stringquery
imageAssetId
stringquery
imageSourceId
stringquery
imageUrl
stringquery
imageVariantId
stringquery
limit
numberquery
mode
enumquerycontains: colors[]. palette: colors[0]/targetVariantId/image. rerank: same + variantIds.
productTypeIds
string[]query
targetVariantId
stringquery
variantIds
string[]query

Response

200One page of color matches (`contains`/`palette`) or a reordered candidate set (`rerank`), discriminated by `mode`.

oneOf

mode: containsobject
gatedOutinteger

Candidates dropped by eligibility, hard filters or required colors.

matchesobject[]
brandstring
brandIdstring
categorystring| null
categoriesstring[]

All original source category IDs in source order; empty when unknown.

codestring| null
colorsobject[]
coveragenumber

Share of the request's colors matched to a distinct swatch, 0 to 1. Never a probability.

imageUrlstring| null
hexstring| null
hasDeclaredColorboolean

True for a paint color card, paint line, primer or coating: the card is the brand's declared hex, never a photo.

productstring
productIdstring
productTypeobject| null
productTypeIdstring| null

Accepted primary specification type ID, independent of source categories; null when unknown.

variantIdstring
variantNamestring
pageInfoobject
endCursorstring| null
hasNextPageboolean
poolTruncatedboolean

True when matches exist past the checked pool: add productTypeIds or brands.

resolvedColorsobject[]
catalogueVariantobject| null
hexstring
indexinteger
kindenum

region: a named meaning's own OKLCh bounds, kept — never collapsed to one hex.

pointregion
sharenumber| null

Image-extracted or stored target-variant swatch share; else null.

unresolvedColorsobject[]
alternativesobject[]
indexinteger
inputstring| null
mode"contains"
mode: paletteobject
gatedOutinteger

Candidates dropped by eligibility, hard filters or required colors.

matchesobject[]
brandstring
brandIdstring
categorystring| null
categoriesstring[]

All original source category IDs in source order; empty when unknown.

codestring| null
colorsobject[]
coveragenumber

Share of the request's colors matched to a distinct swatch, 0 to 1. Never a probability.

imageUrlstring| null
hexstring| null
hasDeclaredColorboolean

True for a paint color card, paint line, primer or coating: the card is the brand's declared hex, never a photo.

productstring
productIdstring
productTypeobject| null
productTypeIdstring| null

Accepted primary specification type ID, independent of source categories; null when unknown.

variantIdstring
variantNamestring
pageInfoobject
endCursorstring| null
hasNextPageboolean
poolTruncatedboolean

True when matches exist past the checked pool: add productTypeIds or brands.

resolvedColorsobject[]
catalogueVariantobject| null
hexstring
indexinteger
kindenum

region: a named meaning's own OKLCh bounds, kept — never collapsed to one hex.

pointregion
sharenumber| null

Image-extracted or stored target-variant swatch share; else null.

unresolvedColorsobject[]
alternativesobject[]
indexinteger
inputstring| null
mode"palette"
mode: rerankobject
searchCatalogueSubsetobject

The corpus the ranked candidates were drawn from — present ONLY when the request narrowed it. ABSENT means the whole graph. The reference is resolved unscoped either way.

descriptionstring

What the scope admits, in one sentence, for a human reader.

scopeenum

Which corpus to answer from. samplizeCatalogue admits published US paint colors 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), 'materialBankEuOrderable' (Material Bank Europe) and 'designShopOrderable' (DesignShop, 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 almost the whole 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 more
excludedBySearchCatalogueSubsetstring[]

Real variant ids outside `searchCatalogueSubset` or the materials catalogue.

referenceobject
candidatesobject[]
colorobject| null
inputobject
resolutionenum
resolvedambiguous
seedobject| null
unresolvedstring[]
matchesobject[]
brandstring
brandIdstring
codestring| null
distancenumber| null

CIEDE2000 difference to the assigned swatch; 0 is identical, null without a palette.

imageUrlstring| null
hexstring| null
productstring
productIdstring
variantIdstring
variantNamestring| null
mode"rerank"
resolvedColorsobject[]
catalogueVariantobject| null
hexstring
indexinteger
kindenum

region: a named meaning's own OKLCh bounds, kept — never collapsed to one hex.

pointregion
sharenumber| null

Image-extracted or stored target-variant swatch share; else null.

Example

curl
curl 'https://beta.materialgraph.com/api/v1/variants/by-color?mode=text' \  -H "Authorization: Bearer $MG_API_KEY"
POST/api/v1/variants/by-vectormg:read

Find variants by semantic or visual similarity

Eligible variants ranked by similarity in the image-lane embedding space (voyage-multimodal-3.5). `query.mode` is `text` (mood words, unanchored), `image` (`query.images`: one to six references, each a catalogue variant or asset, an external URL, or an upload) or `text_and_image` (each image is embedded fused with the text). Several images are combined into one query vector, the mean of their normalised vectors, normalised again; every seed image's variant is left out of the results, and the vector is never returned. `references` echoes the images used, in order. `filters` is the `{terms, numbers, presence, anyField}` grammar `list_variants` takes, less its `orText` and `unstated` additions; a category, brand or variant-id condition rides Pinecone's native metadata, any other condition builds the eligible set in Postgres first and hands it to Pinecone as an allowlist. Every returned id is re-checked through `list_variants` itself with the caller's filters — what that re-check rejects is counted in `scan.rejected`, never hidden. An upload is embedded once and its vector is kept, so a later image-only query on the same upload reads it and spends nothing; with `text` the image is embedded fused with the words, and a URL is embedded on every request. Each image that has to be embedded consumes one request of the key's own `embed` limit, 10 a minute unless the key has another; a catalogue variant seed or a kept upload vector consumes none. Requires the `mg:read` bearer scope.

Request body

attributesstring[]

Attribute or block ids to add.

excludeVariantIdsstring[]

Variant ids to leave out. A seed image's variant is always left out.

filtersobject

AND conditions. field: attribute id, variant, product, brand, category, product_type, lifecycle.

anyFieldobject[]

One of values in any of fields (term attributes).

numbersobject[]
presenceobject[]
termsobject[]
limitinteger

Default 20.

queryobject
imagesobject[]

One to 6 image references, each with exactly one field. Several are combined into one query, and every seed image's variant is left out. Only for image modes. Each image that has to be embedded spends one of the embed requests.

modeenum
textimagetext_and_image
textstring

Description, mood words included. Only for text modes.

searchCatalogueSubsetenum

Only this corpus scope. Absent or all: every variant.

allsamplizeCataloguematerialBankOrderablematerialBankNaOrderablematerialBankNaAndCataloguematerialBankEuOrderable+1 more

Response

200Eligible variants, most similar first, with the scan and timing receipts.
excludedBySearchCatalogueSubsetstring[]

Filtered variant ids outside the materials catalogue.

itemsobject[]
categoryobject| null

The first original source category, or null when unknown.

categoriesobject[]

All original source categories in source order.

productTypeobject| null

The accepted primary specification type, or null for a color sample or unresolved subject.

sampleSubjectenum
materialpaint_color_sampleunknownconflict
colorOptionValueIdstring| null
hasDeclaredColorboolean

True for a paint color card, paint line, primer or coating: the card is the brand's declared hex, never a photo.

codestring| null

The brand's item code.

hexstring| null

The settled card color hex, or null when unknown. Paint cards show this color and never a photo.

imageobject| null

The settled material card image. Always null for paint color samples and paint/coating product types.

brandobject

The brand. `id` is the slug get_brand takes.

namestring

The variant's own name.

productobject
urlstring| null

The brand's page for it, or null when none is known.

variantIdstring
materialBankIdstring| null
notApplicableobject

Requested blocks or attributes this product type does not carry.

preferenceobject
specificationsobject
variantCountinteger

Eligible variants of this product. Absent: none counted.

similaritynumber

Voyage cosine to the query. Ranks only; not a probability.

modeenum
textimagetext_and_image
namedPaintobject

The paint query.text names exactly, by a brand's paint name or code, when the catalogue holds it; the items are what looks like the words. Absent otherwise.

brandstring
colorstring| null
imageUrlstring| null
hexstring| null
productstring
variantIdstring
variantNamestring| null
referencesobject[]

The image references the query used, in order. Empty in text mode.

idstring
kindenum
urlassetIdvariantIdsourceId
scanobject
exhaustedboolean

True when no further candidates match; false means more may exist.

readinteger
rejectedinteger
routeenum
lanemetadataallowlist
timingsobject
embedMsnumber
filterMsnumber
referenceMsnumber
totalMsnumber
vectorMsnumber
verifyMsnumber
unresolvedVariantIdsstring[]
unstatedstring[]

Filtered attributes no variant in scope states at all.

Example

curl
curl -X POST 'https://beta.materialgraph.com/api/v1/variants/by-vector' \  -H "Authorization: Bearer $MG_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "query": {    "mode": "text"  }}'
GET/api/v1/variants/sampled-togethermg:read

Find sampled together — what designers sampled together with your seeds

The materials designers sampled together with one or more seeds in real Material Bank projects (a sample order shows sampling, not specification), other brands first, then the most projects. Give `variantIds` and/or `materials` (free-text names, resolved to one variant each; the resolved seeds come back in `seeds`, and any reference that matched nothing in `unresolved`), at most 12 together. Each row carries `lift` (the mean over the seeds it pairs with; above 1 is above average), `seedMatchCount` (how many seeds it pairs with), `relation` (`other_brand` is another brand, `brand_sibling` and `colorway` share a seed's) and a one-line `reason`. Ranking is other brands first, then `seedMatchCount`, then lift weighted by the projects behind it, one row per product. `projectType` reads one sector from the closed project-type vocabulary; an unknown sector is refused by name. A page is lean: `include` adds `image` (`imageUrl`) and `color` (`swatches`, `colorFit`, which annotates and never reorders). Sample cards are left out unless `includeSamplingArtifacts` is true, an ops and audit switch. Requires the `mg:read` bearer scope.

Parameters

include
enum[]queryOptional row fields: image adds imageUrl, color adds swatches.
limit
integerquery
materials
string[]queryNames to resolve when you hold no ids. Misses come back unresolved.
projectType
enumqueryOnly materials sampled together in projects of this sector.
variantIds
string[]querySeed variants to read.
includeSamplingArtifacts
enumquery

Response

200The sampled-together materials, other brands first, the resolved seeds, and any references that matched nothing.
excludedBySearchCatalogueSubsetstring[]

Seed ids outside the materials catalogue.

sampledTogetherobject[]
brandstring
colorFitnumber| null

Palette likeness to the seeds. Never reorders.

imageUrlstring| null
hexstring| null
liftnumber

Mean lift over the seeds it pairs with; above 1 is above average.

productstring
reasonstring

Why it was suggested, in one line.

relationenum

other_brand is another brand; brand_sibling and colorway share the seed's.

other_brandbrand_siblingcolorway
seedMatchCountinteger

How many seeds it was sampled with.

swatchesobject[]
variantIdstring
variantNamestring| null
seedsobject[]
brandstring
imageUrlstring| null
hexstring| null
productstring
resolvedFromstring

The id or name this seed came from.

variantIdstring
variantNamestring| null
unresolvedstring[]

References that matched no variant.

Example

curl
curl 'https://beta.materialgraph.com/api/v1/variants/sampled-together' \  -H "Authorization: Bearer $MG_API_KEY"
POST/api/v1/variants/resolvemg:read

Resolve a manufacturer page URL or code to a stored variant

Resolves a manufacturer page URL, or a manufacturer code qualified by that URL or a brandId, to a stored variant, its brand, bounded candidates or a named unresolved reason. Exact and scoreless; the submitted URL is never persisted. The body is the `resolve_variant` tool's own output.

Request body

brandIdstring

Brand id from list_brands; qualifies code when no url.

codestring

The brand's own code for the variant, exactly as the brand shows it.

labelsstring[]

Product names shown on the page, to tell URL candidates apart.

urlstring

The brand's product page URL, query and fragment intact.

Response

200The bounded result: identified, placed, ambiguous or unresolved, with evidence.

oneOf

outcome: identifiedobject
evidenceobject[]
detailstring
kindenum
brand-urlbrand-homepagebrand-codevisible-label
urlstring<uri>
receiptobject
normalizationStepsstring[]
normalizedUrlstring<uri>
submittedUrlstring<uri>
outcome"identified"
subjectobject
brandobject
codestring
idstring
kind"variant"
namestring
outcome: placedobject
evidenceobject[]
detailstring
kindenum
brand-urlbrand-homepagebrand-codevisible-label
urlstring<uri>
receiptobject
normalizationStepsstring[]
normalizedUrlstring<uri>
submittedUrlstring<uri>
outcome"placed"
subjectobject
brandobject
codestring
idstring
kind"brand"
namestring
outcome: ambiguousobject
evidenceobject[]
detailstring
kindenum
brand-urlbrand-homepagebrand-codevisible-label
urlstring<uri>
receiptobject
normalizationStepsstring[]
normalizedUrlstring<uri>
submittedUrlstring<uri>
candidatesobject[]
brandobject
codestring
idstring
kindenum
variantbrand
namestring
outcome"ambiguous"
truncatedboolean
outcome: unresolvedobject
evidenceobject[]
detailstring
kindenum
brand-urlbrand-homepagebrand-codevisible-label
urlstring<uri>
receiptobject
normalizationStepsstring[]
normalizedUrlstring<uri>
submittedUrlstring<uri>
outcome"unresolved"
reasonenum
no-stored-urlno-code-match

Example

curl
curl -X POST 'https://beta.materialgraph.com/api/v1/variants/resolve' \  -H "Authorization: Bearer $MG_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "brandId": "…",  "code": "…"}'

Materials

GET/api/v1/materials/{variantId}/blocksmg:read

List a material's canonical blocks

Returns the canonical block completeness and status projection for one variant: canonical block ids and names, field values and statuses, and missing attribute ids. A variant's detail changes when an ingest lands, so success responses carry `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Parameters

variantId
stringpathThe variant id to resolve.

Response

200The material's canonical block projection.
blocksobject[]
canonicalBlockIdstring
canonicalBlockNamestring
fieldsobject[]
missingAttributeIdsstring[]
statusenum
completepartialmissingnot_applicable
variantIdstring

Example

curl
curl 'https://beta.materialgraph.com/api/v1/materials/{variantId}/blocks' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/materials/{variantId}/documentsmg:read

List an owning Product's technical documents

Returns a paginated, URL-deduplicated union of Product- and Variant-grained technical documents. Every Variant declaration retains its contributing Variant ids and states whether it applies to the requested Variant. Coverage distinguishes an explicit empty source collection from a collection not yet ingested, and unavailable documents never carry a usable URL. Requires the `mg:read` bearer scope.

Parameters

variantId
stringpathThe variant id to resolve.
cursor
stringqueryOpaque nextCursor returned by the previous page.
limit
integerquery

Response

200One page of the owning Product's document union.
coverageobject
productUnionobject
requestedVariantobject
sourcesobject[]
documentsobject[]
appliesToRequestedVariantboolean
contentTypestring| null
declarationsobject[]
declaredScopesvariant[]
kind: productobject
kind: variantobject
documentTypesenum[]
technical_data_sheetproduct_sheettechnical_bulletinsustainability_reportspecificationinstallation_guide+17 more
fileExtensionstring| null
idstring
originalNamestring
scopeobject
sourceobject
statusenum
downloadedlinked_not_downloadedunavailable_sourcedead_linkfailed_ingest
urlstring| null
variantAttributionstring[]
nextCursorstring| null
totalDocumentsinteger
variantIdstring

Example

curl
curl 'https://beta.materialgraph.com/api/v1/materials/{variantId}/documents' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/catalogsmg:read

List retained catalog documents

Lists immutable catalog editions with their source, page, render, extraction, and failure receipts. `availability=source_unavailable` keeps a known edition visible without inventing a document; `document.contentScope` distinguishes a retained full publication from cover-only evidence. Live registry truth carries `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Response

200The retained catalog-document shelf.
catalogsvariant[]
kind: documentobject
brandobject
editionobject
idstring
kind"document"
productStatusenum
partialnot_processed
titlestring
availability"available"
documentobject
receiptobject
kind: documentobject
brandobject
editionobject
idstring
kind"document"
productStatus"not_processed"
titlestring
availability"source_unavailable"
documentnull
receiptobject
source"verified-source-material-registry"

Example

curl
curl 'https://beta.materialgraph.com/api/v1/catalogs' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/catalogs/{catalogId}/pagesmg:read

List stable pages in a catalog edition

Returns stable one-based page identities and explicit representation coverage. A page whose derived WebP is not retained carries `status=unavailable`, the required representation version and media type, and no URL or fabricated object key. A known edition with no retained source returns `source_unavailable`. Requires the `mg:read` bearer scope.

Parameters

catalogId
stringpathThe catalog edition id returned by `GET /api/v1/catalogs`.
limit
integerquery
offset
integerquery

Response

200The page list or explicit source-unavailable result.

oneOf

status: availableobject
catalogobject
brandobject
editionobject
idstring
kind"document"
productStatusenum
partialnot_processed
titlestring
availability"available"
documentobject
receiptobject
pagesvariant[]
Variant 1object
catalogIdstring
documentIdstring
editionIdstring
idstring
originalobject
pageobject
representationobject
Variant 2object
catalogIdstring
documentIdstring
editionIdstring
idstring
originalobject
pageobject
representationobject
paginationobject
countinteger
hasMoreboolean
limitinteger
offsetinteger
totalinteger
representationCoverageobject
availableinteger
failuresByReasonobject[]
totalinteger
unavailableinteger
status"available"
reason: retained_source_not_foundobject
catalogobject
brandobject
editionobject
idstring
kind"document"
productStatus"not_processed"
titlestring
availability"source_unavailable"
documentnull
receiptobject
reason"retained_source_not_found"
status"source_unavailable"

Example

curl
curl 'https://beta.materialgraph.com/api/v1/catalogs/{catalogId}/pages' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/catalogs/{catalogId}/referencesmg:read

List verified products in a retained catalog

Returns either every reviewed product/page relationship for one available catalog or an explicit `source_unavailable` result. Each verified reference carries the exact manufacturer code printed in the publication and a normalized anchor for that printed label. `productRegion` is present only when a distinct product-image rectangle has its own explicit review receipt; it is never inferred from the label anchor. Live registry truth carries `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Parameters

catalogId
stringpathThe retained catalog id returned by `GET /api/v1/catalogs`.

Response

200The reviewed product/page index or explicit source-unavailable result.

oneOf

source: verified-source-material-registryobject
catalogobject
brandobject
editionobject
idstring
kind"document"
productStatusenum
partialnot_processed
titlestring
availability"available"
documentobject
receiptobject
coverageobject
documentPagesobject
pagesWithReferencesinteger
precisionobject
productCoverageobject
referencesinteger
unmatchedobject
referencesobject[]
anchorobject
brandobject
catalogueobject
documentobject
editionobject
isPrimaryboolean
pageobject
productRegionobject
sourceobject
status"verified"
variantIdstring
source"verified-source-material-registry"
status"available"
reason: retained_source_not_foundobject
catalogobject
brandobject
editionobject
idstring
kind"document"
productStatus"not_processed"
titlestring
availability"source_unavailable"
documentnull
receiptobject
reason"retained_source_not_found"
status"source_unavailable"

Example

curl
curl 'https://beta.materialgraph.com/api/v1/catalogs/{catalogId}/references' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/catalog-pages/{pageId}/representationmg:read

Read a catalog page representation receipt

Returns a retained page artifact when available, the exact expected artifact without a URL when absent, or `unresolved_page` for an unknown page id. Requires the `mg:read` bearer scope.

Parameters

pageId
stringpathThe opaque page id returned by the catalog page list.

Response

200The retained, unavailable, or unresolved page representation result.

anyOf

status: availableobject
artifactobject
bucket"materialgraph"
keystring
mediaType"image/webp"
urlstring<uri>
pageobject
catalogIdstring
documentIdstring
editionIdstring
idstring
originalobject
pageobject
representationobject
status"available"
reason: retained_representation_not_foundobject
pageobject
catalogIdstring
documentIdstring
editionIdstring
idstring
originalobject
pageobject
representationobject
reason"retained_representation_not_found"
requiredArtifactobject
mediaType"image/webp"
version"catalogue-page-webp-v1"
status"unavailable"
status: unresolved_pageobject
pageIdstring
status"unresolved_page"

Example

curl
curl 'https://beta.materialgraph.com/api/v1/catalog-pages/{pageId}/representation' \  -H "Authorization: Bearer $MG_API_KEY"
POST/api/v1/catalogues/referencesmg:read

Find verified catalogue pages for material variants

Returns exact, human-reviewed relationships between opaque variant ids and pages in immutable brand publications. Every requested id receives an explicit result: `verified`, `no_verified_reference`, `unresolved_variant`, `unavailable`, or `upstream_failure`. A `no_verified_reference` result carries a closed `reason` (`no_publication_on_record`, `publication_source_unavailable`, `publication_not_reviewed`, `product_not_yet_referenced`) and, for every reason but the first, the brand `publication` it was judged against. `no_publication_on_record` means only that the registry holds no publication for the brand, not that the brand publishes none, and no reason says the product is absent from the printed book. A generic brand PDF is never treated as page evidence, and an unreviewed relationship is never guessed. `coverage` reconciles exactly with the deduplicated results. The read reflects the current product graph and therefore carries `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Request body

variantIdsstring[]

Response

200One explicit catalogue-reference result per unique requested variant id.
coverageobject
noVerifiedReferenceinteger
requestedinteger
unavailableinteger
unresolvedVariantinteger
upstreamFailureinteger
verifiedinteger
resultsvariant[]
status: verifiedobject
referenceobject
status"verified"
variantIdstring
status: no_verified_referenceobject
publicationobject
reasonenum
no_publication_on_recordpublication_source_unavailablepublication_not_reviewedproduct_not_yet_referenced
status"no_verified_reference"
variantIdstring
status: unresolved_variantobject
status"unresolved_variant"
variantIdstring
status: unavailableobject
status"unavailable"
variantIdstring
status: upstream_failureobject
status"upstream_failure"
variantIdstring

Example

curl
curl -X POST 'https://beta.materialgraph.com/api/v1/catalogues/references' \  -H "Authorization: Bearer $MG_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "variantIds": [    "…"  ]}'
GET/api/v1/catalogues/collectionsmg:read

List product collections in the catalogue channel

Returns a flat, paginated shelf of product collections whose variants have governed catalogue-channel membership. Each collection includes exact current product and variant counts plus a bounded representative sample. Requires the `mg:read` bearer scope.

Parameters

limit
integerquery
offset
integerquery
sampleLimit
integerquery

Response

200The current catalogue-channel collection shelf.
channelobject
basis"governed_variant_membership"
token"catalogue"
collectionsobject[]
homepageUrlstring| null
idstring
namestring
productCountinteger
sampleProductsobject[]
slugstring
variantCountinteger
countsobject
basis"exact_current_channel_membership"
paginationobject
countinteger
hasMoreboolean
limitinteger
offsetinteger
totalinteger

Example

curl
curl 'https://beta.materialgraph.com/api/v1/catalogues/collections' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/catalogues/documents/{documentId}mg:read

Resolve access to an immutable catalogue document

Resolves one opaque catalogue document id to browser-safe reader and original URLs, content type, and byte-range capability. Unknown or unavailable documents remain explicit discriminated results rather than guessed URLs. The access decision is read at request time and carries `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Parameters

documentId
stringpathThe opaque catalogue document id returned by a verified reference.

Response

200The available access details or an explicit unresolved/unavailable state.

oneOf

status: availableobject
accessobject
byteRangesenum
supportedunknown
contentType"application/pdf"
documentIdstring
originalUrlstring<uri>
readerUrlstring<uri>
documentIdstring
status"available"
status: unresolved_documentobject
documentIdstring
status"unresolved_document"
status: unavailableobject
documentIdstring
status"unavailable"
status: forbiddenobject
documentIdstring
status"forbidden"
status: upstream_failureobject
documentIdstring
status"upstream_failure"

Example

curl
curl 'https://beta.materialgraph.com/api/v1/catalogues/documents/{documentId}' \  -H "Authorization: Bearer $MG_API_KEY"

Pairings

GET/api/v1/materials/{variantId}/pairingsmg:read

Get the "what pairs with this material" rail

A tiered rail: real co-specification evidence (`source: "cospec"`) first, board curation (`source: "considered"`) only when sampled evidence is unavailable, then color palette-similarity (`source: "color"`). EVIDENCE AND ORDERING ARE INDEPENDENT: `order` sorts only within the fixed `cospec` → `considered` → `color` precedence; tiers are never blended or relabelled. `relevance` is anchor-color similarity descending. `lightness_asc` is dark-to-light (canonical OKLab L 0→1); `lightness_desc` is light-to-dark (L 1→0). Missing/unmeasurable values are always last, with `variantId` ascending as the stable tie-break. Sorting and the optional Material Bank corpus gate occur before limit/cursor pagination. HONESTY CONTRACT: no source is true semantic pairing relevance — `cospec` is demand-lift co-occurrence, `considered` is board activity, and `color` is a look-alike. Never present a `considered` or `color` item as "sampled together". Success responses carry the `material-detail` window, `Cache-Control: no-store` (same invalidation event as `/materials/{variantId}`). Requires the `mg:read` bearer scope.

Parameters

variantId
stringpathThe variant id to resolve.
categories
stringqueryComma-separated Material Bank category IDs or exact source paths from list_categories. Filters both pairing arms against actual ordered source memberships, including descendants, within corpus admission.
limit
integerquery
sampledOnly
booleanqueryReturn sampled order evidence only, without board or color fallback.
order
enumqueryGraph-owned ordering within each fixed evidence tier. Omit to preserve the legacy rail.
searchCatalogueSubset
enumqueryFlags each item's `inScope` against this search catalogue subset: true in, false out, null when the graph cannot say (a channel scope on a variant whose channels were never ingested). It never narrows the rail; absent, items carry no `inScope`. The retired `corpus` gate is a 400.
cursor
stringqueryOpaque continuation bound to the seed, categories, order, tier, and last total-order tuple. Requires `order`.

Response

200The pairings rail.
seedMaterialTile
availabilityenum

Orderability: 'available' (listed in the catalog of record), 'discontinued' (withdrawn from that catalog — not a reviewed manufacturer-discontinuation claim), 'superseded' (a reviewed successor relationship exists and the material is no longer listed), 'unknown' (never checked).

availablediscontinuedsupersededunknown
brandstring
categorystring| null

The first original source category name, or null when unknown.

categoriesstring[]

All accepted source category ids in original source order; empty when unknown.

colorobject
colorNamestring

The accepted Color Name attribute — the colorway's true name where variants.name collapsed to the product's; absent means the attribute is absent (MG-940).

imageSourceenum| null
r2origin
imageUrlstring| null
productstring
sampleableboolean

Whether a physical sample is explicitly known to be orderable. Absent means unknown, never unavailable.

variantIdstring
variantNamestring| null
itemsvariant[]
source: cospecobject
brandstring
inScopeboolean| null
brandSlugstring
categorystring| null
colorstring| null
customerCountnumber
demandobject| null
imageUrlstring| null
hexstring| null
liftnumber
liftLabelstring
productstring
productIdstring
projectCountnumber
relationenum
other_brandbrand_siblingcolorway
topProjectTypesobject[]
variantIdstring
variantNamestring| null
rankingobject
source"cospec"
source: consideredobject
brandstring
inScopeboolean| null
brandSlugstring
categorystring| null
colorstring| null
customerCountnumber
demandobject| null
imageUrlstring| null
hexstring| null
liftnumber
liftLabelstring
productstring
productIdstring
projectCountnumber
relationenum
other_brandbrand_siblingcolorway
topProjectTypesobject[]
variantIdstring
variantNamestring| null
rankingobject
source"considered"
source: colorobject
availabilityenum

Orderability: 'available' (listed in the catalog of record), 'discontinued' (withdrawn from that catalog — not a reviewed manufacturer-discontinuation claim), 'superseded' (a reviewed successor relationship exists and the material is no longer listed), 'unknown' (never checked).

availablediscontinuedsupersededunknown
brandstring
categorystring| null

The first original source category name, or null when unknown.

categoriesstring[]

All accepted source category ids in original source order; empty when unknown.

colorobject
colorNamestring

The accepted Color Name attribute — the colorway's true name where variants.name collapsed to the product's; absent means the attribute is absent (MG-940).

imageSourceenum| null
r2origin
imageUrlstring| null
productstring
sampleableboolean

Whether a physical sample is explicitly known to be orderable. Absent means unknown, never unavailable.

variantIdstring
variantNamestring| null
inScopeboolean| null
rankingobject
scorenumber
source"color"
cospecCountinteger

How many items came from real order evidence (the cospec arm).

skippedCandidatesinteger

Candidates dropped at hydration for violating the material contract; present only when > 0. A malformed row costs itself, never the rail (MG-985).

orderingPairingOrdering
anchorColorBasis"one_minus_oklab_euclidean_distance"
appliedenum
relevancelightness_asclightness_desc
colorSpace"oklab"
directionenum
ascendingdescending
lightnessRangeobject
missingValues"last"
requestedenum
relevancelightness_asclightness_desc
tieBreak"variantId_asc"
nextCursorstring

Opaque continuation when another ordered page exists.

Example

curl
curl 'https://beta.materialgraph.com/api/v1/materials/{variantId}/pairings' \  -H "Authorization: Bearer $MG_API_KEY"

Associations

GET/api/v1/applicabilitymg:read

Read manufacturer-declared finish applicability

Reads manufacturer-declared finish applicability without changing entity grain. A subject remains the Product or Variant named by the source; every finish remains a Variant. Results are partitioned by source authority and locator, each with its own coverage state, count kind, and source revision, so consumers never infer that an empty result means the source declared none and never invent precedence between sources. Reverse reads start from a finish Variant. Active facts only. Requires the `mg:read` bearer scope.

Parameters

subjectKind
enumqueryThe exact graph grain of the requested subject.
subjectId
stringqueryOpaque Product or Variant id; pass it back unchanged.
direction
enumquery`forward` reads finishes for a Product or Variant; `reverse` reads application subjects for a finish Variant.

Response

200Source-scoped applicability facts and explicit collection coverage.
directionenum
forwardreverse
kind"manufacturer_declared_applicability"
requestedSubjectoneOf
kind: productobject
idstring
kind"product"
kind: variantobject
idstring
kind"variant"
sourcesobject[]
coverageobject| null
factRevisionsstring[]
factsobject[]
scopeobject
unresolvedobject[]

Example

curl
curl 'https://beta.materialgraph.com/api/v1/applicability?subjectKind=text&subjectId=text&direction=text' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/associated/finishesmg:read

List the finishes a manufacturer declares for a product

"What finishes can I get on this chair?" - the finish variants (fabrics, leathers, surfaces) a manufacturer states are available on the product variant given as `variantId`. DECLARED, NOT INFERRED: every association is one the manufacturer enumerated in their own catalog data, so the response is stamped `kind: "declared_association"` and shares no field with the `/pairings` rail - do not merge the two sets or present one as the other. Each association identifies its endpoint `role` (`finish` in the forward read, `product` in the reverse read) and carries the directed `relation: "applicable_finish"`, so the role is available from the item payload itself. Each edge carries a `declared` block: `basis: "manufacturer_declared"`, `source: "warehouse"`, and a `confidence` that is a FIXED convention value (0.9), never a measurement - it cannot rank anything. `totalAssociations` is the TRUE count for this subject, direction and corpus scope; `associations` is one page of it, so render "(N)" from the total but walk `nextCursor` before claiming anything about the whole set. Each association carries `productTypeReceipt`: `accepted`, `proposed`, or `unknown`; proposed entries are review evidence and must never be rendered as an accepted category. A variant that exists and declares nothing is a 200 with an empty page and a zero total; only an unresolvable id is a 404. These edges change only when an ingest lands, so success responses carry `Cache-Control: no-store` - a window justified by that event and set from the read surface's one central cache policy, not guessed per route. `totalAssociations` does not force the response uncached: it is a property of this subject's own record and moves on the same ingest as the body carrying it. Requires the `mg:read` bearer scope.

Parameters

variantId
stringqueryThe PRODUCT variant to read finishes for. Opaque - pass back exactly the id you were given.
limit
integerqueryHow many associations to return on this page. Associations are browsed wider than pairings, so the page is wider: 1–48, default 24.
searchCatalogueSubset
enumqueryNarrows the associations, `totalAssociations` and the cursor to one search catalogue subset, the same vocabulary search and brands take. `materialBankOrderable` keeps associations pointing at a variant carrying a Material Bank id; a channel scope drops a variant whose channels were never ingested. Absent or `all` is the whole declared set. Membership does not establish sampleability; use `sampleableOnly` for that. The retired `corpus` parameter is a 400.
sampleableOnly
booleanqueryWhen true, return only associations whose effective accepted `sampleable` value is true. False and absent preserve the complete declared set; unknown values are excluded only by this explicit true-only filter.
cursor
stringqueryOpaque keyset continuation - pass the previous response's `nextCursor` unchanged. A cursor is bound to its subject, direction and search catalogue subset; replaying it against a different one is a 400, never a silently different page.

Response

200The declared finish associations for this product.
associationsobject[]
brandstring
colorwayCountinteger

The eligible variants of this association's product, the set `list_variants` pages, so a follow-up list returns this many. Absent when the product has no eligible variant.

declaredobject
hexstring| null
imageUrlstring| null

Canonical thumbnail (same policy as siblings/pairings); null when there is none.

productstring
productTypeReceiptoneOf
state: acceptedobject
state: proposedobject
state: unknownobject
kindLabelstring| null

The accepted display category (the variant's primary product type), for a card's kind pill. Null when none is accepted; never a proposal.

priceTierstring

The variant's accepted `price_tier` term (`$` to `$$$$$`). Absent means unknown, never cheap.

relation"applicable_finish"
roleenum
finishproduct
sampleableboolean

Whether a physical sample is explicitly known to be orderable. Absent means unknown, never unavailable.

variantIdstring
variantNamestring| null
kind"declared_association"

Constant discriminator. These are manufacturer-declared facts; `/pairings` items are inferred companionship. Never merge the two sets.

nextCursorstring

Opaque continuation for the same subject, direction and corpus scope; absent on the last page.

returnedCountinteger

How many associations this page carries.

subjectobject
brandstring
productstring
variantIdstring
variantNamestring| null
totalAssociationsinteger

The TRUE number of declared associations for this subject and direction under the requested corpus scope, not the size of this page. Safe to render as "Compatible Finishes (N)"; `associations` is one page of that set, so walk `nextCursor` before making any claim about the whole set.

Example

curl
curl 'https://beta.materialgraph.com/api/v1/associated/finishes?variantId=text' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/associated/productsmg:read

List the products a manufacturer declares a finish for

"What furniture takes this fabric?" - the product variants a manufacturer states the finish variant given as `variantId` is available on. The exact reverse of `/api/v1/associated/finishes` over the same directed edge: one stored relationship answers both questions, so the two directions can never disagree. DECLARED, NOT INFERRED: every association is one the manufacturer enumerated in their own catalog data, so the response is stamped `kind: "declared_association"` and shares no field with the `/pairings` rail - do not merge the two sets or present one as the other. Each association identifies its endpoint `role` (`finish` in the forward read, `product` in the reverse read) and carries the directed `relation: "applicable_finish"`, so the role is available from the item payload itself. Each edge carries a `declared` block: `basis: "manufacturer_declared"`, `source: "warehouse"`, and a `confidence` that is a FIXED convention value (0.9), never a measurement - it cannot rank anything. `totalAssociations` is the TRUE count for this subject, direction and corpus scope; `associations` is one page of it, so render "(N)" from the total but walk `nextCursor` before claiming anything about the whole set. Each association carries `productTypeReceipt`: `accepted`, `proposed`, or `unknown`; proposed entries are review evidence and must never be rendered as an accepted category. A variant that exists and declares nothing is a 200 with an empty page and a zero total; only an unresolvable id is a 404. These edges change only when an ingest lands, so success responses carry `Cache-Control: no-store` - a window justified by that event and set from the read surface's one central cache policy, not guessed per route. `totalAssociations` does not force the response uncached: it is a property of this subject's own record and moves on the same ingest as the body carrying it. Requires the `mg:read` bearer scope.

Parameters

variantId
stringqueryThe FINISH variant to read products for. Opaque - pass back exactly the id you were given.
limit
integerqueryHow many associations to return on this page. Associations are browsed wider than pairings, so the page is wider: 1–48, default 24.
searchCatalogueSubset
enumqueryNarrows the associations, `totalAssociations` and the cursor to one search catalogue subset, the same vocabulary search and brands take. `materialBankOrderable` keeps associations pointing at a variant carrying a Material Bank id; a channel scope drops a variant whose channels were never ingested. Absent or `all` is the whole declared set. Membership does not establish sampleability; use `sampleableOnly` for that. The retired `corpus` parameter is a 400.
sampleableOnly
booleanqueryWhen true, return only associations whose effective accepted `sampleable` value is true. False and absent preserve the complete declared set; unknown values are excluded only by this explicit true-only filter.
cursor
stringqueryOpaque keyset continuation - pass the previous response's `nextCursor` unchanged. A cursor is bound to its subject, direction and search catalogue subset; replaying it against a different one is a 400, never a silently different page.

Response

200The declared product associations for this finish.
associationsobject[]
brandstring
colorwayCountinteger

The eligible variants of this association's product, the set `list_variants` pages, so a follow-up list returns this many. Absent when the product has no eligible variant.

declaredobject
hexstring| null
imageUrlstring| null

Canonical thumbnail (same policy as siblings/pairings); null when there is none.

productstring
productTypeReceiptoneOf
state: acceptedobject
state: proposedobject
state: unknownobject
kindLabelstring| null

The accepted display category (the variant's primary product type), for a card's kind pill. Null when none is accepted; never a proposal.

priceTierstring

The variant's accepted `price_tier` term (`$` to `$$$$$`). Absent means unknown, never cheap.

relation"applicable_finish"
roleenum
finishproduct
sampleableboolean

Whether a physical sample is explicitly known to be orderable. Absent means unknown, never unavailable.

variantIdstring
variantNamestring| null
kind"declared_association"

Constant discriminator. These are manufacturer-declared facts; `/pairings` items are inferred companionship. Never merge the two sets.

nextCursorstring

Opaque continuation for the same subject, direction and corpus scope; absent on the last page.

returnedCountinteger

How many associations this page carries.

subjectobject
brandstring
productstring
variantIdstring
variantNamestring| null
totalAssociationsinteger

The TRUE number of declared associations for this subject and direction under the requested corpus scope, not the size of this page. Safe to render as "Compatible Finishes (N)"; `associations` is one page of that set, so walk `nextCursor` before making any claim about the whole set.

Example

curl
curl 'https://beta.materialgraph.com/api/v1/associated/products?variantId=text' \  -H "Authorization: Bearer $MG_API_KEY"

Catalog

GET/api/v1/catalog/generationmg:read

Read the current catalogue generation

Authenticated metadata read, read directly from Redis. No SQL and no rate-limit charge. Cache-Control: no-store. A generation outage returns 503.

Response

200The current catalogue generation.
generationinteger

Example

curl
curl 'https://beta.materialgraph.com/api/v1/catalog/generation' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/catalogues/samplize/productsmg:read

Read the complete Samplize paint catalogue

Version-pinned pagination over Snowflake products reconciled with US storefront publication. Includes out-of-stock products, selected paint hexes, color analysis, source hex comparisons and size variants. Optional exact variantIds and collectionHandle filters intersect before pagination; total counts matching eligible records. Unknown or ineligible requested IDs are omitted without substitution. Repeat the same filters and sourceVersion on subsequent pages; omit after when changing filters. Cursors are variant-ID positions, not filter-bound tokens. Collect total unique variant IDs across all pages to establish completeness.

Parameters

limit
integerquery
after
stringquery
variantIds
string[]queryExact canonical identities, sent as repeated query parameters.
collectionHandle
stringqueryExact Samplize source collection handle, not a manufacturer-authored collection.
sourceVersion
stringqueryRequired after the first page; use its sourceVersion.

Response

200A page of canonical paint colors.
completeboolean
searchCatalogueSubset"samplizeCatalogue"
itemsobject[]
brandobject
codestring
collectionMembershipsobject[]
colorAnalysisobject
demandobject| null
hexstring
hexObservationsobject[]
hexSourceobject
lrvnumber| null
lrvObservationsobject[]
lrvSourceobject| null
namestring
oklabobject
productIdstring
publishedHexesobject[]
sizeVariantsobject[]
sourceProductIdstring| null
urlstring| null
variantIdstring
nextCursorstring| null
schemaVersion"2"
sourceobject
sourceVersionstring
totalinteger
unavailableFieldsstring[]

Example

curl
curl 'https://beta.materialgraph.com/api/v1/catalogues/samplize/products' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/catalog/attributesmg:read

Browse or find attribute definitions

One bounded page of attribute definitions, browsed or found by id, name, misspelling or meaning ("double rubs", "reduces echo"). With no `query` a page lists the scope by name then id. With `query` it ranks exact ids, names and aliases first, then near spellings the meaning also supports, then matches by meaning alone, then near spellings alone; each item's `match` says which method found it, and a distance or similarity compares only within one method. `blockIds`, `categoryIds` and `filterableOnly` narrow the scope before any matching and combine with AND; an empty list keeps everything. Each item carries what picks the next call: its `filterOps` are the operators a `/api/v1/variants` filter accepts on it (empty when it cannot filter on it), and its `dictionaryId` names the value dictionary it draws from. Deprecated attributes are left out. Pass `pageInfo.endCursor` as `cursor` with the same `query` and scope for the next page. A query is embedded per request, so this is a dynamic, Node-only endpoint; the answer depends on the registry and the vocabulary corpus rather than on the caller, so success responses carry `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Parameters

query
stringqueryAn attribute id, name, misspelling or meaning, e.g. "double rubs" or "reduces echo". At most 200 characters.
blockIds
stringqueryComma-separated block ids, e.g. `identity,material_composition`. Keeps attributes in any of them.
categoryIds
stringqueryComma-separated Material Bank category ids or exact source paths from `/api/v1/categories`. Keeps attributes stated by actual member variants at these nodes or below them.
filterableOnly
booleanqueryWhen `true`, keep only attributes a `/api/v1/variants` filter can use.
limit
integerqueryItems per page, 1 to 50.
cursor
stringquerypageInfo.endCursor from the previous page, with the same query and scope.

Response

200One bounded page of attribute definitions.
itemsobject[]
blockIdsstring[]
descriptionstring
dictionaryIdstring| null
filterOpsenum[]

list_variants ops; empty when it cannot filter on it.

innot_inallexistsmissingeq+4 more
filterableboolean
idstring
matchobject

Present only when the page answers a query.

namestring
statedByinteger

Live variants stating this, in the categories or all; 0 means none.

unitIdstring| null
valueKindstring
liveVariantsinteger

Live variants in the categories, or all without categoryIds.

outsideScopeobject[]

Page one only: matches outside blockIds ranking above the best item.

blockIdsstring[]
idstring
namestring
pageInfoobject
endCursorstring| null
hasNextPageboolean

Example

curl
curl 'https://beta.materialgraph.com/api/v1/catalog/attributes' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/catalog/enum-valuesmg:read

Browse or find a value dictionary's allowed values

One bounded page of the allowed values of a value dictionary. With `dictionaryId` and no `query` a page lists that dictionary's active values by label, then id. With `query` it finds values by name, alias, misspelling or meaning ("class a", "brushed", "for outdoor use") in the named dictionary or, with no `dictionaryId`, in every dictionary; each item's `match` says which method found it (exact, alias, fuzzy or vector), and a distance or similarity compares only within one method. Take `dictionaryId` from an attribute's `dictionaryId` (`/api/v1/catalog/attributes`). An item's `valueId` is the bare term id a `/api/v1/variants` filter accepts on any of its `attributeIds`; an item with no `attributeIds` cannot be filtered on. Only active terms are offered, and only terms the materials catalogue serves. Pass `pageInfo.endCursor` as `cursor` with the same `query` and `dictionaryId` for the next page. A query is embedded per request, so this is a dynamic, Node-only endpoint; the answer depends on the registry and the vocabulary corpus rather than on the caller, so success responses carry `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Parameters

dictionaryId
stringqueryA value dictionary id from an attribute's `dictionaryId`. Required without a `query`; with a `query`, omit it to search every dictionary.
query
stringqueryA value name, alias, misspelling or meaning, e.g. "class a" or "for outdoor use". Omit to browse a dictionary.
limit
integerqueryItems per page, 1 to 50.
cursor
stringquerypageInfo.endCursor from the previous page, with the same query and dictionaryId.

Response

200One bounded page of a dictionary's allowed values.
dictionaryIdstring| null
itemsobject[]
attributeIdsstring[]
definitionstring| null
dictionaryIdstring
labelstring
matchobject
valueIdstring
pageInfoobject
endCursorstring| null
hasNextPageboolean

Example

curl
curl 'https://beta.materialgraph.com/api/v1/catalog/enum-values' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/categoriesmg:read

Browse or search the category tree

One bounded page of the original Material Bank category hierarchy, at any depth. With no parameters it lists the roots; `parentId` alone lists direct children. With `query` it searches the selected node and its descendants, or the whole source hierarchy, by exact id, source path or name, then lexical phrases and near spellings. Matching is lexical and in memory; it does not query a category vector index. A match does not include its children: call again with its id as `parentId`. Each node carries its exact source `path`, zero-based `depth` and `hasChildren`. The canonical `id` is also a `product_category` dictionary term; each node has an exact source `path`. Category filters select actual source memberships at that node or below it, within the variant corpus admission policy and independently of specification product types. Pass `pageInfo.nextCursor` as `cursor` with the same `query` and `parentId`. Ships `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Parameters

query
stringqueryA Material Bank category id, source path or name, e.g. "wallpaper" or "Materials.Surfaces.Laminate".
parentId
stringqueryA category id from `/api/v1/categories`. Alone, lists that category's immediate children. With `query`, searches that category and everything under it.
limit
integerqueryItems per page, 1 to 50.
cursor
stringquerypageInfo.nextCursor from the previous page, with the same query and parentId.

Response

200One bounded page of matching categories.
itemsobject[]
hasChildrenboolean
idstring
matchobject

Present only when the page answers a query.

namestring
parentIdstring| null
pathstring
depthinteger
noticestring

Present only when the query is refused as a category.

pageInfoobject
nextCursorstring| null
returnedCountinteger
totalMatchesinteger

Example

curl
curl 'https://beta.materialgraph.com/api/v1/categories' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/categories/{id}mg:read

Get one category by id

One original Material Bank category by canonical id or exact source path: its name, exact `path`, zero-based `depth`, ancestors and direct children. The tree supports arbitrary depth. The canonical id is the category filter key and a `product_category` dictionary term. Percent-encode the entire id or source path when placing it in this URL, including spaces, ampersands and slashes. An unknown category id or source path is a 404. Product category assertions preserve every original source membership in source order: `category` is the first, `categories` is the full array, and missing source hierarchy is unknown (`category: null`, `categories: []`). Product assertions come from source parents; explicit source-backed variant overrides carry each variant's own categories. Derived product browse membership from children is membership, not a parent assertion. `productType` separately conveys specification routing. Pure and in memory; ships `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Parameters

id
stringpathA category id or exact source path from `/api/v1/categories`, such as "brick" or "Materials.Surfaces.Laminate". Percent-encode the entire route segment with encodeURIComponent(id).

Response

200The resolved category.
aliasesstring[]
ancestorsobject[]
idstring
namestring
childrenobject[]
idstring
namestring
descriptionstring| null
formerNamesstring[]
idstring
kind"category"
namestring
pathstring
depthinteger

Example

curl
curl 'https://beta.materialgraph.com/api/v1/categories/{id}' \  -H "Authorization: Bearer $MG_API_KEY"

Schema

GET/api/v1/schema/value-dictionariesmg:read

List canonical value dictionaries

The closed VOCABULARIES behind every `enum` attribute — the exact tokens a spec query may use, and the aliases each token absorbs. Per dictionary, `terms` carries `{ id, label, aliases?, broaderTermId?, externalMappings?, metadata? }`: `id` is the canonical token to send, `label` is the human name to show, `aliases` are the brand-specific spellings the graph folds into it (this is the normalisation, made inspectable), and `broaderTermId` links a narrow term to its parent so you can widen a query that returns too little. Reach for this when a criterion is rejected or returns nothing — the value is usually right in spirit and wrong in spelling. To find one term from a phrase rather than read the full dump, `GET /api/v1/catalog/enum-values` (the `list_enum_values` tool) ranks the same terms. Items carry governance: `semanticId` is the stable global identifier (`mg:attribute.nrc_rating`) that survives renames, and `lifecycle` is `{ status: draft | active | deprecated, introducedIn, deprecatedIn?, supersededBy? }` — prefer `active` ids, and follow `supersededBy` rather than reusing a deprecated one. Envelope: `{ kind, schemaVersion, items, totalCount }` — `schemaVersion` is the registry's own version (not the OpenAPI spec version), and `totalCount` is the complete registry size, never a page (these reads are unpaginated). Deterministic: the registry is compiled into the deploy, so the same deploy always answers identically and responses carry the shared vocabulary window, `Cache-Control: no-store` — a deploy invalidates it for free, so a five-minute floor with revalidation is more honest than the day-long literal this used to claim. Requires the `mg:read` bearer scope.

Response

200List canonical value dictionaries
kind"valueDictionaries"
schemaVersionstring
itemsobject[]
authorityoneOf
kind: taxonomyobject
kind: colorscopeobject
kind: materialgraphobject
kind: external-standardobject
descriptionstring
idstring
namestring
termsobject[]
lifecycleobject
semanticIdstring
totalCountinteger

Example

curl
curl 'https://beta.materialgraph.com/api/v1/schema/value-dictionaries' \  -H "Authorization: Bearer $MG_API_KEY"

Brands

GET/api/v1/brandsmg:read

List brands

One cursor-paged page of canonical brands that carry materials, browsed by name or found by name, alias or misspelling. Each item carries `brandId`, the brand's slug and the value `list_variants`' `brand` filter takes, with `name` and `website`. Without a query a page lists the scope by name, then id. With one, exact and alias matches rank first and near spellings follow; every brand a name matches is returned, so an ambiguous name is the caller's to disambiguate. Follow `pageInfo.endCursor` for the next page, passing the same `query`, `categoryId` and `searchCatalogueSubset`. Requires the `mg:read` bearer scope.

Parameters

query
stringqueryA brand name, alias or misspelling, e.g. "Kvadrat". Empty to browse.
categoryId
stringqueryA `list_categories` id. Keeps brands with an eligible variant in that category scope.
searchCatalogueSubset
enumqueryKeeps brands with at least one eligible variant in this corpus, by the same per-variant test `list_variants` applies. ABSENT (or `all`) means every brand that carries materials; absence is never a narrower default. `materialBankNaOrderable`, `materialBankEuOrderable` and `designShopOrderable` are transaction-oriented; `materialBankNaAndCatalogue` is a governed discovery corpus and does not claim every member is buyable; `samplizeCatalogue` admits published US Samplize paint colors regardless of stock. `materialBankOrderable` keeps brands with a variant holding a Material Bank SKU, which is provenance rather than a storefront and nearly every brand in the feed has one. An unknown value is a 400 naming it.
limit
integerqueryBrands per page.
cursor
stringqueryThe previous page's `pageInfo.endCursor`, with the same `query`, `categoryId` and `searchCatalogueSubset`. Empty for the first page.

Response

200The requested page of canonical brands.
itemsobject[]
brandIdstring
namestring
websitestring| null

The brand's homepage, when on record.

pageInfoobject
endCursorstring| null
hasNextPageboolean

Example

curl
curl 'https://beta.materialgraph.com/api/v1/brands' \  -H "Authorization: Bearer $MG_API_KEY"

Sources

POST/api/v1/source-materialmg:readmg:source

Upload a source-material batch — a heterogeneous pile of files

Accepts one atomic multipart batch as a PILE of source files: a repeatable `files` field carrying any mix of PDF (≤30 MiB, ≤250 pages), plain-text/markdown (≤2 MiB), and images (JPEG, PNG, WebP, or AVIF, each ≤8 MiB) — at least one file, at most 16, ≤128 MiB aggregate. There is no need to pre-sort the files: the pipeline classifies each one and stamps its receipt `role` (`brief`, `reference_image`, `document`, `document_image`, `inspiration_image`, or `unclassified`). Bytes are validated strictly (media type, magic bytes, PDF structure and page count) before any write; any invalid file rejects the whole batch with per-file reasons in the problem `issues` member and nothing is stored. On success the bytes are stored content-addressed and a typed receipt is returned. Requires the `mg:source` bearer scope (source-material uploads — a `source` key suffices; ingest and admin keys also carry it).

Request body

filesstring<binary>[]

The heterogeneous pile: any mix of PDF, text/markdown, and images (≥1, ≤16 files across the whole batch).

Response

201The stored batch receipt.
batchIdstring

The stored batch id (`smb_…`).

sourcesobject[]
sourceIdstring

The stored file id (`smf_…`).

roleenum

The pipeline-assigned role. `unclassified` is a legitimate terminal value when the file could not be placed; clients should tolerate new values.

briefreference_imagedocumentdocument_imageinspiration_imageunclassified
filenamestring
mediaTypestring
sha256string

Lowercase hex SHA-256 of the stored bytes.

bytesinteger
pagesinteger| null

PDF page count; null for images.

Example

curl
curl -X POST 'https://beta.materialgraph.com/api/v1/source-material' \  -H "Authorization: Bearer $MG_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "files": [    "…"  ]}'

Runs

POST/api/v1/workflows/{workflowId}/runsmg:readmg:compose or mg:write

Start an asynchronous workflow Run

Starts one registered workflow. Validates its input and scope, transactionally records the Run and encrypted outbox command, then acknowledges without executing inline. Only registered workflows are startable; any other id is a 404. Supply `Idempotency-Key` or `idempotencyKey` to make retries safe. Requires either the `mg:compose` or the `mg:write` scope. Starting a workflow additionally requires whatever scope that workflow declares.

Parameters

workflowId
stringpathThe id of a registered Mastra workflow, such as `edit-collection`.
Idempotency-Key
stringheaderStable caller key for idempotent Run creation.

Request body

inputJsonobject
idempotencyKeystring
clientReferencestring

Response

200An idempotent replay of an existing Run.
acceptedboolean
replayedboolean
workflowRunIdstring
runPublicRun
idstring
workflowIdstring

The workflow this run belongs to, the same id its path names.

clientReferencestring| null
statusenum
queuedclaimedrunningsuspendedsucceededfailed+1 more
redactedInputJsonobject
redactedOutputJsonobject| null
usageJsonobject| null
artifactRefsobject[]| null
failureJsonPublicRunFailure| null
suspensionPayloadobject| null

The current typed question payload while `status` is `suspended`; null otherwise. The same exact payload is retained on the `suspended` ledger event.

suspensionStepIdstring| null

The step awaiting input while `status` is `suspended` — pass it back as `stepId` when resuming, so a stale client cannot resume a step the run has already moved past. Read `suspensionPayload` for the current typed question. The same exact payload is retained on the `suspended` ledger event for event-stream consumers.

eventCursorinteger
attemptsinteger
startedAtstring<date-time>| null
cancellationRequestedAtstring<date-time>| null
completedAtstring<date-time>| null
createdAtstring<date-time>
updatedAtstring<date-time>

Example

curl
curl -X POST 'https://beta.materialgraph.com/api/v1/workflows/{workflowId}/runs' \  -H "Authorization: Bearer $MG_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "inputJson": {}}'
GET/api/v1/workflows/{workflowId}/runs/{runId}mg:read

Get a Run's current state

Returns the caller-owned redacted Run ledger record, read under the workflow it belongs to. Prompts, raw model messages, reasoning, credentials, provider payloads, and source-document contents are never returned. Requires `mg:read`.

Parameters

workflowId
stringpathThe id of a registered Mastra workflow, such as `edit-collection`.
runId
stringpathThe public Run identifier returned by `POST /api/v1/workflows/{workflowId}/runs`.

Response

200The current Run state.
runPublicRun
idstring
workflowIdstring

The workflow this run belongs to, the same id its path names.

clientReferencestring| null
statusenum
queuedclaimedrunningsuspendedsucceededfailed+1 more
redactedInputJsonobject
redactedOutputJsonobject| null
usageJsonobject| null
artifactRefsobject[]| null
failureJsonPublicRunFailure| null
suspensionPayloadobject| null

The current typed question payload while `status` is `suspended`; null otherwise. The same exact payload is retained on the `suspended` ledger event.

suspensionStepIdstring| null

The step awaiting input while `status` is `suspended` — pass it back as `stepId` when resuming, so a stale client cannot resume a step the run has already moved past. Read `suspensionPayload` for the current typed question. The same exact payload is retained on the `suspended` ledger event for event-stream consumers.

eventCursorinteger
attemptsinteger
startedAtstring<date-time>| null
cancellationRequestedAtstring<date-time>| null
completedAtstring<date-time>| null
createdAtstring<date-time>
updatedAtstring<date-time>

Example

curl
curl 'https://beta.materialgraph.com/api/v1/workflows/{workflowId}/runs/{runId}' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/workflows/{workflowId}/runs/{runId}/eventsmg:read

Stream ordered Run events

Streams caller-owned, redacted Run events as server-sent events (SSE). Each frame uses the Run event sequence as `id`, the event type as `event`, and the serialized public Run event as `data`. Resume after the last observed sequence with the `after` query parameter or `Last-Event-ID` header; `after` takes precedence when both are supplied. The server polls for new events about once per second and returns at most 100 events per read. A terminal `succeeded`, `failed`, or `canceled` event closes the stream immediately. Non-terminal streams close after at most 25 seconds, so clients must reconnect with their cursor. The route emits no SSE heartbeat/comment frames or `retry` directive; the bounded connection lifetime is its reconnect and liveness boundary. Requires `mg:read`.

Parameters

workflowId
stringpathThe id of a registered Mastra workflow, such as `edit-collection`.
runId
stringpathThe public Run identifier returned by `POST /api/v1/workflows/{workflowId}/runs`.
after
integerqueryStream events whose sequence is greater than this non-negative cursor. Takes precedence over `Last-Event-ID` when both are supplied.
include
enumquery`all` adds the workflow engine's own telemetry frames (`inngest.*`) to the stream, for diagnosing an outage. Absent: domain events only.
Last-Event-ID
integerheaderStandard SSE reconnect cursor. Stream events whose sequence is greater than this non-negative integer when `after` is absent.

Response

200An SSE stream of ordered public Run events. Reconnect after the bounded stream closes unless a terminal event was received.
string

SSE frames encoded as `id: <sequence>`, `event: <type>`, and `data: <PublicRunEvent JSON>`, followed by a blank line.

Example

curl
curl 'https://beta.materialgraph.com/api/v1/workflows/{workflowId}/runs/{runId}/events' \  -H "Authorization: Bearer $MG_API_KEY"

Meta

GET/api/v1/whoamimg:read

Echo the authenticated principal and scopes

A sanity-check endpoint: fire it with your key to confirm the credential is accepted and see which principal and scopes the platform resolved it to, before spending a real query. Echoes the auth context verbatim — no database. Identity is per-request, so responses carry `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Response

200The resolved principal and scopes.
principalstring

The resolved principal for the credential (e.g. `apikey:read`).

scopesstring[]

The scopes granted to this credential (e.g. `mg:read`).

Example

curl
curl 'https://beta.materialgraph.com/api/v1/whoami' \  -H "Authorization: Bearer $MG_API_KEY"
GET/api/v1/keys/usagemg:read

Get this key's usage and spend

What the presented credential has spent, and on what. The path carries no key id and never will: a key reads its own usage and no other's. Reads a rollup at (day × operation class) grain — there are no raw per-call rows anywhere, so the answer is bounded and cheap, and a class the key never used produces no line rather than a zero one. Costs are carried in `costMicroDollars` (millionths of a dollar), which is the exact stored unit; cents would round a single search down to zero. Budgets are stated in cents because that is the unit an operator sets. Enforcement is two-tier and every `byOperation` entry says which tier it got and why. `hard` classes (`search_standard`, `search_pro`, `embed_image`, `embed_text`, `materials_read`, `revit_export`) are priced before the work runs, so an over-budget call is refused with 402 `budget_exceeded` and never executes. `soft` classes (`run_low`, `run_medium`, `run_high`, `live_verify`) have a true cost only once the work finishes, so they run and are booked afterwards — a key at its cap can exceed it by at most one such operation, and `budget.overshootPossible` says whether that exposure is live. Rates are placeholders and are not prices anyone has agreed. Spend is a live counter, so responses carry `Cache-Control: no-store`. Requires the `mg:read` bearer scope.

Parameters

from
string<date>queryInclusive first day of the window, `YYYY-MM-DD`. Defaults to the start of the current billing period.
to
string<date>queryEXCLUSIVE last day of the window, `YYYY-MM-DD`. Defaults to tomorrow, so today's partial usage is included. At most 62 days per call.

Response

200The key's usage rollup and the budget it is measured against.
budgetobject

The cap this usage is measured against, and exactly how strongly it is enforced.

budgetCentsinteger| null

This key's cap for one billing period, in whole cents. `null` = unbudgeted.

periodStartstring

First day of the current billing period, `YYYY-MM-DD`.

spentCentsnumber| null

Spend booked in the current period, in cents. Fractional on purpose — half a cent is half a cent.

remainingCentsnumber| null

Cap minus spend. Negative after a soft-tier overshoot. `null` when unbudgeted.

softTierClassesstring[]

Operation classes whose cost is knowable only after the work runs. These are booked post-hoc and can take the key past its cap by at most one operation each.

overshootPossibleboolean

True while a budgeted key still has allowance exposed to that post-hoc overshoot. False when the key is unbudgeted (nothing to overshoot).

byOperationobject[]

The same money folded by class.

costDollarsnumber
costMicroDollarsinteger
enforcementenum

`hard` — the budget was a real pre-check and an over-budget call of this class was refused before it ran. `soft` — the true cost existed only after the work completed, so the call ran and was booked afterwards.

hardsoft
enforcementReasonstring

Why this class gets that tier. Stated, never implied.

operationstring
quantityinteger
dailyobject[]

Every non-empty (day, operation class) cell, day then class order.

costDollarsnumber

The same figure in dollars, to six decimal places.

costMicroDollarsinteger

Cost in millionths of a dollar — the exact stored unit. Cents would round a single search to zero.

operationstring

The operation class, e.g. `search_standard`.

quantityinteger

Whole operations charged that day.

usageDatestring

The UTC day the operations were charged on, `YYYY-MM-DD`.

fromstring

Inclusive first day of the window.

tostring

Exclusive last day of the window.

totalCostDollarsnumber
totalCostMicroDollarsinteger

Example

curl
curl 'https://beta.materialgraph.com/api/v1/keys/usage' \  -H "Authorization: Bearer $MG_API_KEY"

Transports

REST, MCP and CLI

All three read the same data. Pick whichever suits the caller: a REST client, an MCP-capable agent or a shell pipe.

REST

/api/v1

Versioned REST, described by an OpenAPI 3.1 document.

MCP

/mcp

Stateless Streamable HTTP, 18 read-only tools. Add it to Claude Code with: claude mcp add --transport http materialgraph https://beta.materialgraph.com/mcp --header "Authorization: Bearer mg_read_..."

CLI

mg

A command-line client with JSON output, NDJSON for pipes and schema introspection.

MCP

MCP tools

18 read-only tools, read from the public MCP server definition when this page renders.

find_sampled_together

Materials designers sampled together with the ones you hold, from real projects.

find_variants_by_color

Find materials by color; mode says how.

find_variants_by_vector

Eligible variants that look or feel like a description (mood words count), an image, or both.

get_attribute

Read one attribute's definition by id: value type, unit, shape, dictionary id, blocks, category grades and the list_variants ops it accepts.

get_brand

Read one brand's stored record by its brandId (from list_brands, never a display name): name, aliases, description, website and logo.

get_category

Read one original Material Bank category by id or exact source path: name, path, depth, ancestors and direct children.

get_variant

Read one variant by id: name, code, product, brand, category, image and page.

list_attributes

Browse or find attribute definitions by id, name, misspelling or meaning, scoped by blocks, categories or filterability.

list_brands

Find brands by name, alias or misspelling, or browse them.

list_categories

Browse or search original Material Bank categories.

list_enum_values

Browse a dictionary's allowed values, or search every dictionary by name, alias, typo or meaning.

list_variant_assets

One variant's images and files with urls, primary first.

list_variant_facets

Facet values and exact variant counts among the same eligible variants list_variants' filters return, never a global vocabulary.

list_variants

Eligible variants, paged.

open_material_library

Open the Material Library to browse and select variants.

resolve_variant

Find the stored variant for a brand's page URL, or for a brand's own code with that URL or a brandId.

search_variants

Reads plain words into category, brand, attribute, number and color filters and returns what it understood (leaveOut unapplies one, never excludes), other meanings, filters and a page.

show_materials

Show materials in an app with photos, brand, values and what was sampled together.

Errors

Errors and rate limits

Every error is an RFC 7807 problem document, served as application/problem+json with a matching X-Request-Id. The /api/v1 prefix is versioned, and the OpenAPI document is version 0.8.0 (pre-1.0), so ignore fields you do not recognise.

401 problem+json
{
  "type": "https://beta.materialgraph.com/errors/unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "No authentication credentials provided",
  "is_retriable": false,
  "trace_id": "3f9c2a1e-8b7d-4e2a-9c1f-2d6b8a4e5c30",
  "suggestions": [
    "Provide a valid API key via the Authorization header (Bearer <key>) or X-API-Key header.",
    "If using JWT authentication, ensure the token is not expired and was issued for the correct audience."
  ]
}

is_retriable says whether repeating the request can succeed, and suggestions lists what to try next, so a client can decide without a person reading the error.

Rate limits are enforced. Responses carry X-RateLimit-* headers, and exceeding the limit returns a 429 with Retry-After.

Error types: the RFC 7807 type values a caller can meet. Each one has a page at /errors.

400ambiguous-brandAmbiguous branddo not retry
404asset-not-foundAsset not founddo not retry
404asset-unavailableAsset unavailabledo not retry
400bad-requestBad requestdo not retry
404brand-not-foundBrand not founddo not retry
402budget-exceededSpend budget exceededdo not retry
409conflictConflictdo not retry
404crawl-not-foundCrawl not founddo not retry
402credits-exhaustedCredits exhausteddo not retry
504deadline-exceededRequest deadline exceededretriable
400empty-brandBrand carries no materialsdo not retry
404endpoint-not-foundNo such endpointdo not retry
403forbiddenForbiddendo not retry
500internal-errorInternal server errorretriable
404match-response-not-foundMatch response not founddo not retry
422match-result-not-in-responseResult not in that match responsedo not retry
404material-not-foundMaterial not founddo not retry
404not-foundResource not founddo not retry
413payload-too-largePayload too largedo not retry
404project-not-foundProject not founddo not retry
429rate-limitedRate limit exceededretriable
499request-abortedRequest aborteddo not retry
404run-not-foundRun not founddo not retry
404schedule-not-foundSchedule not founddo not retry
409search/stale-cursorStale search cursordo not retry
503service-unavailableService unavailableretriable
404source-material-batch-not-foundSource-material batch not founddo not retry
404source-material-source-not-foundSource-material file not founddo not retry
401unauthorizedAuthentication requireddo not retry
400unknown-brandUnknown branddo not retry
502upstream-failureUpstream service failureretriable
409visual-cluster-build-lockedVisual-cluster build lock heldretriable
409visual-cluster-fit-requires-workstationFitting runs from the workstation CLIretriable
422visual-cluster-not-activatableVisual-cluster snapshot not activatabledo not retry
404visual-cluster-not-foundVisual cluster not founddo not retry
409visual-cluster-run-not-resumableVisual-cluster run not resumableretriable

Discovery links