Product catalog

The public index every mockup, buy button, and margin keys off — read it with any HTTP client.

Applies to @snowcone-app/sdk@0.17.0

Read a product

The catalog is a standard Meilisearch index. Every product has a code — its id field, the same code you put in a mockup or buy URL — so you fetch it directly by code. The read key is the public, read-only search key — safe to ship in client code, like your Shop ID. This is the supported public catalog path, not an implementation detail: the index name, the key, and the product shape below are the same contract the SDK ships types for (CatalogProduct). Products you can’t render yet have an empty mockups — filter them out with "filter": "mockups IS NOT EMPTY" (the SDK’s listProducts() does this for you).

curl https://search.snowcone.app/indexes/snowcone/documents/BEEB77 \
  -H "Authorization: Bearer eee819b849798ad9091228c486ec05d0931e5292"
The read key above is required. The raw search.snowcone.app host is a standard Meilisearch instance: an anonymous request returns 401 missing_authorization_header, and a shop API key or an sk_ secret key is not the catalog key — sending one returns 403 invalid_api_key. Use the public read key shown here, or skip keys entirely with the SDK’s getProduct() / listProducts() (it carries the public key for you).

What a product holds

One record carries everything the rest of the docs build on: the price your margins start from, the placements you can print on, and the options and variants a shopper picks.

json
{
  "id": "BEEB77",                      // the product code you put in a mockup or buy URL
  "name": "Framed Canvas",
  "description": "Gallery-quality framed print…",
  "price": 2399,                       // MSRP in cents — your suggested retail
  "mockups": [                         // scene renders; mockups[].id is the opaque
    { "id": "FV1qjO",                  //   mockupId for realtime — NOT a placement name
      "gvids": ["Pv1sLC"] },           // the variants this scene photographs — a scene
    { "id": "liMnIy",                  //   only renders variants listed in its gvids
      "gvids": ["Pv1sLC"] }
  ],
  "placements": [                      // printable areas; match one by "label" (exact case)
    { "label": "Front", "key": "front", "type": "image", "width": 800, "height": 1000 }
  ],
  "options": {                         // size / color / … → see Options
    "attributes": {
      "Size":  { "choices": [{ "label": "5x7" }, { "label": "8x10" }] },
      "Frame": { "choices": [{ "label": "Walnut", "hex": "#5b3d1d" }, { "label": "Oak", "hex": "#b0814d" }] }
    },
    "combinations": [                  // each combination → the variantId you render/buy
      { "Size": "5x7", "Frame": "Walnut", "price": 2375, "variantId": "Pv1sLC" }
    ]
  },
  "variants": [                        // resolved variants you can render or buy
    { "gvid": "Pv1sLC", "placements": [{ "label": "Front", "width": 800, "height": 1000 }] }
  ]
}
Match a placement by its label exactly as the catalog returns it — capitalized "Front", not "front" (that lowercase value is the separate key field). A mismatched name silently renders a blank placement. Likewise, mockups[].id values (e.g. FV1qjO) are opaque scene codes — pass those, not placement names, as mockupIds for realtime renders. Each scene also lists gvids — the variants it photographs. The default variant works everywhere, but if you force a specific variant=, pick a scene whose gvids include it (see realtime).
price is in cents and is the MSRP — your suggested retail and your default cost. Turning it into profit is the Pricing & margins page.

Where it leads

Each field on a product opens onto its own concept: