Instant mockups
A mockup is a URL
The whole platform rests on one idea: a mockup is just an image URL. Three values — a product, your artwork, and your Shop ID — and you have your art on real, buyable merchandise. It’s an <img> source, so you can build it in any language with no SDK and no server to stand up.
<img src="https://img.snowcone.app/hoodie-black?asset=https%3A%2F%2Fcdn.example.com%2Fart.png&shop=YOUR_SHOP_ID" />const asset = encodeURIComponent("https://cdn.example.com/art.png");
const src = `https://img.snowcone.app/hoodie-black?asset=${asset}&shop=YOUR_SHOP_ID`;
// drop `src` into any <img>curl -L "https://img.snowcone.app/hoodie-black?asset=…&shop=YOUR_SHOP_ID" -o mockup.avifasset = urllib.parse.quote("https://cdn.example.com/art.png", safe="")
src = f"https://img.snowcone.app/hoodie-black?asset={asset}&shop=YOUR_SHOP_ID"Your shop was created when you signed up — copy its ID at snowcone.app/studio/api-keys — public, like a Cloudinary cloud name. No account? Mint a sandbox shop on Get a Shop ID.
Try it
Change the product, artwork, and params below and watch the real render update. The URL it builds is the exact one you’d ship — copy it, swap in your own Shop ID and asset URL, and drop it into an <img>.
Live render · demo shop (rate-limited)
https://img.snowcone.app/BEEB77?asset=https%3A%2F%2FdqMwU8Jct3.storage.snowcone.app%2Fdemo%2Fdemo-008.jpg&shop=YOUR_SHOP_IDWhy “instant”
Nothing is pre-computed. A brand-new image — one a shopper uploaded or an AI generated a second ago — renders on the fly in under a second, so you can show people their own art on merchandise at scale with no pipeline to manage. The URL is the render request and the cache key at once: the first hit renders, every hit after is served from the edge.
Anatomy of the URL
The product is the path; everything else is a query param. Only asset and shop are required — the rest default from the product’s catalog record when omitted.
https://img.snowcone.app / hoodie-black ? asset=… & shop=YOUR_SHOP_ID
└──────── host ────────┘ └─ product ─┘ └ artwork ┘ └─── Shop ID ───┘
# Optional, all defaulted from the catalog if omitted:
# &placement=back which print area → Multiple placements
# &variant=L_BLK which size/color → Options
# &mockup=FV1qjO which scene/angle
# &width=1200 render width (snapped down; default 1400)
# &aspect=2:3 center-crop to portrait — the only crop; omit it and
# the image keeps the scene's native shape
# &signature=… L3 signed URL → Signed URLsRender width
Add &width= to control resolution. Requests snap down to the largest allowed width that doesn’t exceed what you asked for — a floor, not a round-to-nearest — so URLs stay a small, stable set of cache keys rather than an infinite range. 900 snaps to 800, not 1000; omit width entirely and you get the default, 1400.
<!-- Ask for a width; the renderer snaps DOWN to the nearest allowed size. -->
<img src="https://img.snowcone.app/hoodie-black?asset=…&shop=YOUR_SHOP_ID&width=1200" />
<!-- allowed: 400 600 800 1000 1200 1400 1600 1800 2000 2500 3000 4000 -->
<!-- asking for 900 snaps to 800, not 1000 -->Displaying mockups
The image keeps its scene’s native shape — 16:9 for most products, square for some — with the product composed centered. Don’t assume the dimensions: use object-fit: cover to fit any frame shape — cropping is safe.
<!-- Fit any frame shape with object-fit: cover. -->
<div style="aspect-ratio: 1 / 1; overflow: hidden;">
<img
src="https://img.snowcone.app/hoodie-black?asset=…&shop=YOUR_SHOP_ID&width=1600"
style="width:100%; height:100%; object-fit:cover;" />
</div>cover crop shows only a slice of the render, so request about 2× the displayed size or it looks blurry: a 320–400px retina card wants width=1400–1600, not 800.To crop in the render instead, pass &aspect=2:3 — it center-crops the scene to portrait, and it’s the only crop the renderer applies. Omit it (or pass 16:9) and no crop happens — you get the full scene at its native shape.
<!-- aspect=2:3 center-crops the scene to portrait. -->
<img src="https://img.snowcone.app/hoodie-black?asset=…&shop=YOUR_SHOP_ID&aspect=2:3" />
<!-- Omit aspect= (or pass 16:9) for the full scene at its native shape. -->cdn.snowcone.app. A plain <img> follows it transparently, but proxies that fetch the URL themselves need to allow both hosts — e.g. Next.js images.remotePatterns must list img.snowcone.app and cdn.snowcone.app.Cost & timing headers
Every render tells you what it cost. The image response carries three headers — whether it was a cache hit or a fresh render, how long the render took, and what it cost in USD — so your very first curl doubles as a bill preview, and a slow page is diagnosable from the response alone.
curl -i -L -o mockup.avif "https://img.snowcone.app/hoodie-black?asset=…&shop=YOUR_SHOP_ID"
# x-render-cache: miss whether this hit rendered fresh (miss) or came from cache (hit)
# x-render-ms: 742 server render time for this image, in milliseconds
# x-render-cost-usd: 0.02 what this render cost, in USD
# A cached repeat returns x-render-cache: hit — it isn't re-rendered (and isn't re-billed).miss once per unique URL and hit after that. The realtime channel reports the same telemetry per render: each result carries renderMs, costUsd, and renderCache.Where it leads
Once the mockup renders, the same URL grammar carries you the rest of the way:

